All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-09 12:59 ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-02-09 12:59 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, devicetree-discuss, linux-doc, linux-kernel,
	linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
Notes:
- During development I used a debugfs clock consumer that I can also
  post if there is interest in it.
- With current (3.8-rc6) common clock framework there is two (minor)
  issues:
  * although clocks are registered with devm_clk_register they are not
    removed from the clock tree on unloading. That makes reloading of
    clk-si5351 as module impossible.
  * potentially there could be more than one different external si5351
    generators but clocks are registered with names that do not refer
    to e.g. the device name. Maybe common clock framework should
    prepend the device name for each registered clock, i.e. 0-0060.clk0.
    That would also avoid name collisions with same clock names from
    different drivers (clk0 is likely to be used by others ;))
- The driver has been frequency tested for some common video/audio
  clocks and manages it to tune in every frequency successfully. A
  comparison with silabs windows tool shows a different heuristic
  for vco frequencies. The tests have been comfirmed by visual
  check on an 500MHz oscilloscope but no jitter measurements have
  been carried out. I will provide comparison by email on request.

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1447 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1727 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..3fa3c3e
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			drive-strength = <8>;
+			multisynth-source = <0>;
+			clock-source = <0>;
+			pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			drive-strength = <4>;
+			multisynth-source = <1>;
+			clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 902b1b1..04bcef2a0 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -46,6 +46,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index ee90e87..92a6fd6 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -26,4 +26,5 @@ obj-$(CONFIG_ARCH_ZYNQ)		+= clk-zynq.o
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..b526742
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1447 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static int si5351_xtal_is_enabled(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	unsigned char reg;
+	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
+	return (reg & SI5351_XTAL_ENABLE) ? 1 : 0;
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+	.is_enabled = si5351_xtal_is_enabled,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+static int si5351_clkin_is_enabled(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned char reg;
+	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
+	return (reg & SI5351_CLKIN_ENABLE) ? 1 : 0;
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else
+		idiv = SI5351_CLKIN_DIV_1;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.is_enabled = si5351_clkin_is_enabled,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+					    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX,
+					    &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5)
+		m = hwdata->params.p1;
+	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
+		m = 4;
+	else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static int si5351_clkout_is_enabled(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	if (val & SI5351_CLK_POWERDOWN)
+		return 0;
+	val = si5351_reg_read(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL);
+	if (val & (1 << hwdata->num))
+		return 0;
+	return 1;
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned char rdiv;
+
+	rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
+		SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.is_enabled = si5351_clkout_is_enabled,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "multisynth-source", &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(
+	struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
+			       GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->clkout = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct clk *), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
+			    &drvdata->onecell);
+
+	dev_info(&client->dev, "registered si5351 i2c client\n");
+
+	return 0;
+}
+
+static int si5351_i2c_remove(struct i2c_client *client)
+{
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.remove = si5351_i2c_remove,
+	.id_table = si5351_i2c_ids,
+};
+
+static int __init si5351_module_init(void)
+{
+	return i2c_add_driver(&si5351_driver);
+}
+module_init(si5351_module_init);
+
+static void __exit si5351_module_exit(void)
+{
+	i2c_del_driver(&si5351_driver);
+}
+module_exit(si5351_module_exit);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-09 12:59 ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-02-09 12:59 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
Notes:
- During development I used a debugfs clock consumer that I can also
  post if there is interest in it.
- With current (3.8-rc6) common clock framework there is two (minor)
  issues:
  * although clocks are registered with devm_clk_register they are not
    removed from the clock tree on unloading. That makes reloading of
    clk-si5351 as module impossible.
  * potentially there could be more than one different external si5351
    generators but clocks are registered with names that do not refer
    to e.g. the device name. Maybe common clock framework should
    prepend the device name for each registered clock, i.e. 0-0060.clk0.
    That would also avoid name collisions with same clock names from
    different drivers (clk0 is likely to be used by others ;))
- The driver has been frequency tested for some common video/audio
  clocks and manages it to tune in every frequency successfully. A
  comparison with silabs windows tool shows a different heuristic
  for vco frequencies. The tests have been comfirmed by visual
  check on an 500MHz oscilloscope but no jitter measurements have
  been carried out. I will provide comparison by email on request.

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1447 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1727 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..3fa3c3e
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			drive-strength = <8>;
+			multisynth-source = <0>;
+			clock-source = <0>;
+			pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			drive-strength = <4>;
+			multisynth-source = <1>;
+			clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 902b1b1..04bcef2a0 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -46,6 +46,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index ee90e87..92a6fd6 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -26,4 +26,5 @@ obj-$(CONFIG_ARCH_ZYNQ)		+= clk-zynq.o
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..b526742
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1447 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static int si5351_xtal_is_enabled(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	unsigned char reg;
+	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
+	return (reg & SI5351_XTAL_ENABLE) ? 1 : 0;
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+	.is_enabled = si5351_xtal_is_enabled,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+static int si5351_clkin_is_enabled(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned char reg;
+	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
+	return (reg & SI5351_CLKIN_ENABLE) ? 1 : 0;
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else
+		idiv = SI5351_CLKIN_DIV_1;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.is_enabled = si5351_clkin_is_enabled,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+					    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX,
+					    &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5)
+		m = hwdata->params.p1;
+	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
+		m = 4;
+	else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static int si5351_clkout_is_enabled(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	if (val & SI5351_CLK_POWERDOWN)
+		return 0;
+	val = si5351_reg_read(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL);
+	if (val & (1 << hwdata->num))
+		return 0;
+	return 1;
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned char rdiv;
+
+	rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
+		SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.is_enabled = si5351_clkout_is_enabled,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "multisynth-source", &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(
+	struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
+			       GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->clkout = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct clk *), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
+			    &drvdata->onecell);
+
+	dev_info(&client->dev, "registered si5351 i2c client\n");
+
+	return 0;
+}
+
+static int si5351_i2c_remove(struct i2c_client *client)
+{
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.remove = si5351_i2c_remove,
+	.id_table = si5351_i2c_ids,
+};
+
+static int __init si5351_module_init(void)
+{
+	return i2c_add_driver(&si5351_driver);
+}
+module_init(si5351_module_init);
+
+static void __exit si5351_module_exit(void)
+{
+	i2c_del_driver(&si5351_driver);
+}
+module_exit(si5351_module_exit);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
  2013-02-09 12:59 ` Sebastian Hesselbarth
  (?)
@ 2013-02-11  5:46   ` Mike Turquette
  -1 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-02-11  5:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth, Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-02-09 04:59:32)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Notes:
> - During development I used a debugfs clock consumer that I can also
>   post if there is interest in it.

Please do.  I have a set of patches that implement a fake clock subtree
for testing the core framework.  I've been thinking of pushing this to
the list once it is more presentable and your work might fit into that
nicely.

> - With current (3.8-rc6) common clock framework there is two (minor)
>   issues:
>   * although clocks are registered with devm_clk_register they are not
>     removed from the clock tree on unloading. That makes reloading of
>     clk-si5351 as module impossible.

This is a known issue.  clk_unregister is a NOP and defining it has
always been deferred until the day that someone needed it.  Care to
take a crack at it?

>   * potentially there could be more than one different external si5351
>     generators but clocks are registered with names that do not refer
>     to e.g. the device name. Maybe common clock framework should
>     prepend the device name for each registered clock, i.e. 0-0060.clk0.
>     That would also avoid name collisions with same clock names from
>     different drivers (clk0 is likely to be used by others ;))

More unfinished work, just like clk_unregister above.  I'm sure you are
aware that clk_register takes struct device *dev as input, but does
nothing with it.  It wouldn't take much to concatenate the device name
and clock name if dev is present.  However a complication here is that
the registration code takes a parent string name to match parents up for
discrete subtrees; how could statically defined data know about the
device name ahead of time?

The above design decision took place before the big DT push we have
today and was short-sighted.  It would be better to change the framework
to rely less on string name lookups and DT is one way out of that.

3.8-rc7 is already out and I don't plan to take anything that hasn't
already been submitted for 3.9 now.  Can you resubmit this after 3.9-rc1
comes out?

Thanks,
Mike

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-11  5:46   ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-02-11  5:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-02-09 04:59:32)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Notes:
> - During development I used a debugfs clock consumer that I can also
>   post if there is interest in it.

Please do.  I have a set of patches that implement a fake clock subtree
for testing the core framework.  I've been thinking of pushing this to
the list once it is more presentable and your work might fit into that
nicely.

> - With current (3.8-rc6) common clock framework there is two (minor)
>   issues:
>   * although clocks are registered with devm_clk_register they are not
>     removed from the clock tree on unloading. That makes reloading of
>     clk-si5351 as module impossible.

This is a known issue.  clk_unregister is a NOP and defining it has
always been deferred until the day that someone needed it.  Care to
take a crack at it?

>   * potentially there could be more than one different external si5351
>     generators but clocks are registered with names that do not refer
>     to e.g. the device name. Maybe common clock framework should
>     prepend the device name for each registered clock, i.e. 0-0060.clk0.
>     That would also avoid name collisions with same clock names from
>     different drivers (clk0 is likely to be used by others ;))

More unfinished work, just like clk_unregister above.  I'm sure you are
aware that clk_register takes struct device *dev as input, but does
nothing with it.  It wouldn't take much to concatenate the device name
and clock name if dev is present.  However a complication here is that
the registration code takes a parent string name to match parents up for
discrete subtrees; how could statically defined data know about the
device name ahead of time?

The above design decision took place before the big DT push we have
today and was short-sighted.  It would be better to change the framework
to rely less on string name lookups and DT is one way out of that.

3.8-rc7 is already out and I don't plan to take anything that hasn't
already been submitted for 3.9 now.  Can you resubmit this after 3.9-rc1
comes out?

Thanks,
Mike

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-11  5:46   ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-02-11  5:46 UTC (permalink / raw)
  To: linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-02-09 04:59:32)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Notes:
> - During development I used a debugfs clock consumer that I can also
>   post if there is interest in it.

Please do.  I have a set of patches that implement a fake clock subtree
for testing the core framework.  I've been thinking of pushing this to
the list once it is more presentable and your work might fit into that
nicely.

> - With current (3.8-rc6) common clock framework there is two (minor)
>   issues:
>   * although clocks are registered with devm_clk_register they are not
>     removed from the clock tree on unloading. That makes reloading of
>     clk-si5351 as module impossible.

This is a known issue.  clk_unregister is a NOP and defining it has
always been deferred until the day that someone needed it.  Care to
take a crack at it?

>   * potentially there could be more than one different external si5351
>     generators but clocks are registered with names that do not refer
>     to e.g. the device name. Maybe common clock framework should
>     prepend the device name for each registered clock, i.e. 0-0060.clk0.
>     That would also avoid name collisions with same clock names from
>     different drivers (clk0 is likely to be used by others ;))

More unfinished work, just like clk_unregister above.  I'm sure you are
aware that clk_register takes struct device *dev as input, but does
nothing with it.  It wouldn't take much to concatenate the device name
and clock name if dev is present.  However a complication here is that
the registration code takes a parent string name to match parents up for
discrete subtrees; how could statically defined data know about the
device name ahead of time?

The above design decision took place before the big DT push we have
today and was short-sighted.  It would be better to change the framework
to rely less on string name lookups and DT is one way out of that.

3.8-rc7 is already out and I don't plan to take anything that hasn't
already been submitted for 3.9 now.  Can you resubmit this after 3.9-rc1
comes out?

Thanks,
Mike

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
  2013-02-11  5:46   ` Mike Turquette
@ 2013-02-11  9:52     ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-02-11  9:52 UTC (permalink / raw)
  To: Mike Turquette
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On 02/11/2013 06:46 AM, Mike Turquette wrote:
> Quoting Sebastian Hesselbarth (2013-02-09 04:59:32)
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> ---
>> Notes:
>> - During development I used a debugfs clock consumer that I can also
>>    post if there is interest in it.
>
> Please do.  I have a set of patches that implement a fake clock subtree
> for testing the core framework.  I've been thinking of pushing this to
> the list once it is more presentable and your work might fit into that
> nicely.

Mike,

then I will clean the debugfs driver and post it together with this
patch for 3.9-rc1 as an individual patch.

>> - With current (3.8-rc6) common clock framework there is two (minor)
>>    issues:
>>    * although clocks are registered with devm_clk_register they are not
>>      removed from the clock tree on unloading. That makes reloading of
>>      clk-si5351 as module impossible.
>
> This is a known issue.  clk_unregister is a NOP and defining it has
> always been deferred until the day that someone needed it.  Care to
> take a crack at it?

Ok. I can have a look at it and propose a patch but that will take a
while as other stuff came in between. But IMHO, preparing/enabling
clocks by clock consumers should increase reference count so referenced
modules cannot be unloaded.. but that I have never had a look at, yet ;)

>>    * potentially there could be more than one different external si5351
>>      generators but clocks are registered with names that do not refer
>>      to e.g. the device name. Maybe common clock framework should
>>      prepend the device name for each registered clock, i.e. 0-0060.clk0.
>>      That would also avoid name collisions with same clock names from
>>      different drivers (clk0 is likely to be used by others ;))
>
> More unfinished work, just like clk_unregister above.  I'm sure you are
> aware that clk_register takes struct device *dev as input, but does
> nothing with it.  It wouldn't take much to concatenate the device name
> and clock name if dev is present.  However a complication here is that
> the registration code takes a parent string name to match parents up for
> discrete subtrees; how could statically defined data know about the
> device name ahead of time?

I see. Wrt the above comment about spare time, would prepending DT
clocks be sufficient? Or/And use a fallback mechanism that first tries
a full match, full match with own device name, and relaxed match for
clock name as it is now?

> The above design decision took place before the big DT push we have
> today and was short-sighted.  It would be better to change the framework
> to rely less on string name lookups and DT is one way out of that.
>
> 3.8-rc7 is already out and I don't plan to take anything that hasn't
> already been submitted for 3.9 now.  Can you resubmit this after 3.9-rc1
> comes out?

Sure, but I'll be not available next 2 weeks or so. If 3.8 falls
within that time, I will re-post it later. It is ok for me, if it has
to go in after 3.9 also.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-11  9:52     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-02-11  9:52 UTC (permalink / raw)
  To: linux-arm-kernel

On 02/11/2013 06:46 AM, Mike Turquette wrote:
> Quoting Sebastian Hesselbarth (2013-02-09 04:59:32)
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> ---
>> Notes:
>> - During development I used a debugfs clock consumer that I can also
>>    post if there is interest in it.
>
> Please do.  I have a set of patches that implement a fake clock subtree
> for testing the core framework.  I've been thinking of pushing this to
> the list once it is more presentable and your work might fit into that
> nicely.

Mike,

then I will clean the debugfs driver and post it together with this
patch for 3.9-rc1 as an individual patch.

>> - With current (3.8-rc6) common clock framework there is two (minor)
>>    issues:
>>    * although clocks are registered with devm_clk_register they are not
>>      removed from the clock tree on unloading. That makes reloading of
>>      clk-si5351 as module impossible.
>
> This is a known issue.  clk_unregister is a NOP and defining it has
> always been deferred until the day that someone needed it.  Care to
> take a crack at it?

Ok. I can have a look at it and propose a patch but that will take a
while as other stuff came in between. But IMHO, preparing/enabling
clocks by clock consumers should increase reference count so referenced
modules cannot be unloaded.. but that I have never had a look at, yet ;)

>>    * potentially there could be more than one different external si5351
>>      generators but clocks are registered with names that do not refer
>>      to e.g. the device name. Maybe common clock framework should
>>      prepend the device name for each registered clock, i.e. 0-0060.clk0.
>>      That would also avoid name collisions with same clock names from
>>      different drivers (clk0 is likely to be used by others ;))
>
> More unfinished work, just like clk_unregister above.  I'm sure you are
> aware that clk_register takes struct device *dev as input, but does
> nothing with it.  It wouldn't take much to concatenate the device name
> and clock name if dev is present.  However a complication here is that
> the registration code takes a parent string name to match parents up for
> discrete subtrees; how could statically defined data know about the
> device name ahead of time?

I see. Wrt the above comment about spare time, would prepending DT
clocks be sufficient? Or/And use a fallback mechanism that first tries
a full match, full match with own device name, and relaxed match for
clock name as it is now?

> The above design decision took place before the big DT push we have
> today and was short-sighted.  It would be better to change the framework
> to rely less on string name lookups and DT is one way out of that.
>
> 3.8-rc7 is already out and I don't plan to take anything that hasn't
> already been submitted for 3.9 now.  Can you resubmit this after 3.9-rc1
> comes out?

Sure, but I'll be not available next 2 weeks or so. If 3.8 falls
within that time, I will re-post it later. It is ok for me, if it has
to go in after 3.9 also.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
  2013-02-09 12:59 ` Sebastian Hesselbarth
@ 2013-02-18 10:19   ` Daniel Mack
  -1 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-02-18 10:19 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Stephen Warren, linux-doc, linux-kernel, Rob Herring,
	Russell King - ARM Linux, Dom Cobley, Mike Turquette,
	Andrew Morton, Rabeeh Khoury, devicetree-discuss,
	linux-arm-kernel

Hi Sebastian,

On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.

Thank you very much for your work! I had to wait for OMAP platforms to
move over to the common clock framework, but with that in place now, I
could finally give it a test.

Some comments below.

> - The driver has been frequency tested for some common video/audio
>   clocks and manages it to tune in every frequency successfully. A
>   comparison with silabs windows tool shows a different heuristic
>   for vco frequencies. The tests have been comfirmed by visual
>   check on an 500MHz oscilloscope but no jitter measurements have
>   been carried out. I will provide comparison by email on request.

I would be interested in more tests, yes. My first checks set up clkout1
to 32.768 KHz, and on my oscilloscope,  I measured some 1.04 KHz only.
Will do some debugging later.

> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static int si5351_xtal_is_enabled(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	unsigned char reg;
> +	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
> +	return (reg & SI5351_XTAL_ENABLE) ? 1 : 0;
> +}

On an AM33xx platform, I got tons of BUGs like this one:


[    5.028525] BUG: scheduling while atomic: swapper/1/0x00000002
[    5.034600] INFO: lockdep is turned off.
[    5.038682] Modules linked in:
[    5.041866] irq event stamp: 136090
[    5.045496] hardirqs last  enabled at (136089): [<c04ae9bc>]
_raw_spin_unlock_irqrestore+0x60/0x68
[    5.054843] hardirqs last disabled at (136090): [<c04ae1f4>]
_raw_spin_lock_irqsave+0x1c/0x60
[    5.063733] softirqs last  enabled at (135026): [<c0039d84>]
__do_softirq+0x150/0x1b4
[    5.071907] softirqs last disabled at (135019): [<c003a18c>]
irq_exit+0x98/0xa0
[    5.079541] [<c0013610>] (unwind_backtrace+0x0/0xf8) from
[<c0058ec8>] (__schedule_bug+0x58/0x78)
[    5.088793] [<c0058ec8>] (__schedule_bug+0x58/0x78) from [<c04ad50c>]
(__schedule+0x3c8/0x45c)
[    5.097773] [<c04ad50c>] (__schedule+0x3c8/0x45c) from [<c04ab3f0>]
(schedule_timeout+0x120/0x1d4)
[    5.107114] [<c04ab3f0>] (schedule_timeout+0x120/0x1d4) from
[<c003f780>] (msleep+0x14/0x20)
[    5.115912] [<c003f780>] (msleep+0x14/0x20) from [<c033dd08>]
(omap_i2c_wait_for_bb+0x68/0xb0)
[    5.124890] [<c033dd08>] (omap_i2c_wait_for_bb+0x68/0xb0) from
[<c033e528>] (omap_i2c_xfer+0x3c/0xfc)
[    5.134503] [<c033e528>] (omap_i2c_xfer+0x3c/0xfc) from [<c033a550>]
(__i2c_transfer+0x44/0x80)
[    5.143575] [<c033a550>] (__i2c_transfer+0x44/0x80) from [<c033bab8>]
(i2c_transfer+0x5c/0xbc)
[    5.152555] [<c033bab8>] (i2c_transfer+0x5c/0xbc) from [<c02b4458>]
(regmap_i2c_read+0x4c/0x6c)
[    5.161625] [<c02b4458>] (regmap_i2c_read+0x4c/0x6c) from
[<c02b1840>] (_regmap_raw_read+0x98/0xdc)
[    5.171056] [<c02b1840>] (_regmap_raw_read+0x98/0xdc) from
[<c02b18e8>] (_regmap_read+0x64/0x9c)
[    5.180215] [<c02b18e8>] (_regmap_read+0x64/0x9c) from [<c02b1964>]
(regmap_read+0x44/0x5c)
[    5.188923] [<c02b1964>] (regmap_read+0x44/0x5c) from [<c036d45c>]
(si5351_clkout_is_enabled+0x74/0xb8)
[    5.198720] [<c036d45c>] (si5351_clkout_is_enabled+0x74/0xb8) from
[<c036a9f4>] (clk_disable_unused_subtree+0x68/0xac)
[    5.209873] [<c036a9f4>] (clk_disable_unused_subtree+0x68/0xac) from
[<c036a9ac>] (clk_disable_unused_subtree+0x20/0xac)

This is because clk_disable_unused_subtree() calls ->is_enabled() with a
spinlock held, but si5351_xtal_is_enabled() will be sleeping, either by
acquiring a mutex in the regmap core, or in the OMAP's i2c transfer
routine. So bottom line is to make this callback atomic, and I did that
locally for now by caching the 'prepare' state of xtal, clkin and the
individual clocks. I can share a patch if you like.

> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5)
> +		m = hwdata->params.p1;
> +	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
> +		m = 4;
> +	else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +	do_div(rate, m);

For p1 == p2 == p3, this will be a DIV0. I encountered this here by not
specifying the clkout2 node. I think it's safe to just bail if m == 0?


Again, thanks a lot for working on this!


Daniel


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-18 10:19   ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-02-18 10:19 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sebastian,

On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.

Thank you very much for your work! I had to wait for OMAP platforms to
move over to the common clock framework, but with that in place now, I
could finally give it a test.

Some comments below.

> - The driver has been frequency tested for some common video/audio
>   clocks and manages it to tune in every frequency successfully. A
>   comparison with silabs windows tool shows a different heuristic
>   for vco frequencies. The tests have been comfirmed by visual
>   check on an 500MHz oscilloscope but no jitter measurements have
>   been carried out. I will provide comparison by email on request.

I would be interested in more tests, yes. My first checks set up clkout1
to 32.768 KHz, and on my oscilloscope,  I measured some 1.04 KHz only.
Will do some debugging later.

> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static int si5351_xtal_is_enabled(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	unsigned char reg;
> +	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
> +	return (reg & SI5351_XTAL_ENABLE) ? 1 : 0;
> +}

On an AM33xx platform, I got tons of BUGs like this one:


[    5.028525] BUG: scheduling while atomic: swapper/1/0x00000002
[    5.034600] INFO: lockdep is turned off.
[    5.038682] Modules linked in:
[    5.041866] irq event stamp: 136090
[    5.045496] hardirqs last  enabled at (136089): [<c04ae9bc>]
_raw_spin_unlock_irqrestore+0x60/0x68
[    5.054843] hardirqs last disabled at (136090): [<c04ae1f4>]
_raw_spin_lock_irqsave+0x1c/0x60
[    5.063733] softirqs last  enabled at (135026): [<c0039d84>]
__do_softirq+0x150/0x1b4
[    5.071907] softirqs last disabled at (135019): [<c003a18c>]
irq_exit+0x98/0xa0
[    5.079541] [<c0013610>] (unwind_backtrace+0x0/0xf8) from
[<c0058ec8>] (__schedule_bug+0x58/0x78)
[    5.088793] [<c0058ec8>] (__schedule_bug+0x58/0x78) from [<c04ad50c>]
(__schedule+0x3c8/0x45c)
[    5.097773] [<c04ad50c>] (__schedule+0x3c8/0x45c) from [<c04ab3f0>]
(schedule_timeout+0x120/0x1d4)
[    5.107114] [<c04ab3f0>] (schedule_timeout+0x120/0x1d4) from
[<c003f780>] (msleep+0x14/0x20)
[    5.115912] [<c003f780>] (msleep+0x14/0x20) from [<c033dd08>]
(omap_i2c_wait_for_bb+0x68/0xb0)
[    5.124890] [<c033dd08>] (omap_i2c_wait_for_bb+0x68/0xb0) from
[<c033e528>] (omap_i2c_xfer+0x3c/0xfc)
[    5.134503] [<c033e528>] (omap_i2c_xfer+0x3c/0xfc) from [<c033a550>]
(__i2c_transfer+0x44/0x80)
[    5.143575] [<c033a550>] (__i2c_transfer+0x44/0x80) from [<c033bab8>]
(i2c_transfer+0x5c/0xbc)
[    5.152555] [<c033bab8>] (i2c_transfer+0x5c/0xbc) from [<c02b4458>]
(regmap_i2c_read+0x4c/0x6c)
[    5.161625] [<c02b4458>] (regmap_i2c_read+0x4c/0x6c) from
[<c02b1840>] (_regmap_raw_read+0x98/0xdc)
[    5.171056] [<c02b1840>] (_regmap_raw_read+0x98/0xdc) from
[<c02b18e8>] (_regmap_read+0x64/0x9c)
[    5.180215] [<c02b18e8>] (_regmap_read+0x64/0x9c) from [<c02b1964>]
(regmap_read+0x44/0x5c)
[    5.188923] [<c02b1964>] (regmap_read+0x44/0x5c) from [<c036d45c>]
(si5351_clkout_is_enabled+0x74/0xb8)
[    5.198720] [<c036d45c>] (si5351_clkout_is_enabled+0x74/0xb8) from
[<c036a9f4>] (clk_disable_unused_subtree+0x68/0xac)
[    5.209873] [<c036a9f4>] (clk_disable_unused_subtree+0x68/0xac) from
[<c036a9ac>] (clk_disable_unused_subtree+0x20/0xac)

This is because clk_disable_unused_subtree() calls ->is_enabled() with a
spinlock held, but si5351_xtal_is_enabled() will be sleeping, either by
acquiring a mutex in the regmap core, or in the OMAP's i2c transfer
routine. So bottom line is to make this callback atomic, and I did that
locally for now by caching the 'prepare' state of xtal, clkin and the
individual clocks. I can share a patch if you like.

> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5)
> +		m = hwdata->params.p1;
> +	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
> +		m = 4;
> +	else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +	do_div(rate, m);

For p1 == p2 == p3, this will be a DIV0. I encountered this here by not
specifying the clkout2 node. I think it's safe to just bail if m == 0?


Again, thanks a lot for working on this!


Daniel

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-19 19:15   ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-02-19 19:15 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Stephen Warren, linux-doc, linux-kernel, Rob Herring,
	Russell King - ARM Linux, Dom Cobley, Mike Turquette,
	Andrew Morton, Rabeeh Khoury, devicetree-discuss,
	linux-arm-kernel

Hi Sebastian,

I did some more tests today and it took me a while to dig for the root
cause why things were not working for me in the first place - see below.


On 09.02.2013 13:59, Sebastian Hesselbarth wrote:

> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator@60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;

As referred to in another thread, registering the ref25M clock that way
didn't suffice for me on my platform - but that's a different story.

> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +	unsigned char reg, struct si5351_parameters *params)
> +{
> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];

On a general note, I think you can use u8 instead of unsigned char all
over the place here, which will save you some indentation trouble.

> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				  unsigned char num, unsigned char parent)
> +{
> +	struct clk *pclk;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case 0:
> +		pclk = drvdata->msynth[num].hw.clk;
> +		break;
> +	case 1:
> +		pclk = drvdata->msynth[0].hw.clk;
> +		if (num >= 4)
> +			pclk = drvdata->msynth[4].hw.clk;
> +		break;
> +	case 2:
> +		pclk = drvdata->xtal.clk;
> +		break;
> +	case 3:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +		pclk = drvdata->clkin.clk;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}

[...]

> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned val;
> +
> +	val = 0;
> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +	switch (index) {
> +	case 0:
> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;

I fugured that _si5351_clkout_reparent() wouldn't actually call
->set_parent() on the clock, which leads to the fact that
CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
end leaf is actually supplied with a new rate, which leads to incorrect
effective clocks, depending on the current multisynth/pll configuration.

The reason for this is in clk_set_parent() itself, which bails if the
parent is already set to the passed value:

	if (clk->parent == parent)
		goto out;

I fixed that for now by explicitly setting the clock's parent to NULL
before calling clk_set_parent() in _si5351_clkout_reparent(), so the
calbacks are triggered. But there might be a nicer way, for example to
factor out the CLK_SET_RATE_PARENT handling to some function called from
_si5351_clkout_reparent() or so.

Anyway, with this hack in place along with the other details I mentioned
in my first mail, the driver seems to work for me now, which is great. I
will do more extensive tests later that week when I have access to
better scopes ...


Many thanks again,
Daniel



> +	case 1:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (hwdata->num == 0 || hwdata->num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case 2:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case 3:
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	}
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INPUT_MASK, val);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned char rdiv;
> +
> +	rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +		rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* powerdown clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.is_enabled = si5351_clkout_is_enabled,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	struct property *prop;
> +	const __be32 *p;
> +	unsigned int num, val;
> +
> +	if (np == NULL)
> +		return;
> +
> +	/*
> +	 * property pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(client->dev.of_node, "pll-source",
> +				 prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			break;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p)
> +			break;
> +
> +		if (_si5351_pll_reparent(drvdata, num, val))
> +			dev_warn(&client->dev,
> +				 "unable to reparent pll %d to %d\n",
> +				 num, val);
> +	}
> +
> +	for_each_child_of_node(client->dev.of_node, np) {
> +		if (of_property_read_u32(np, "reg", &num)) {
> +			dev_err(&client->dev, "missing reg property of %s\n",
> +				np->full_name);
> +			continue;
> +		}
> +
> +		if (of_property_read_bool(np, "pll-master"))
> +			_si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +		if (!of_property_read_u32(np, "drive-strength", &val)) {
> +			if (_si5351_clkout_set_drive_strength(drvdata,
> +							      num, val))
> +				dev_warn(&client->dev,
> +					 "unable to set drive strength of %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +			if (_si5351_msynth_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent multisynth %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-source", &val)) {
> +			if (_si5351_clkout_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent clockout %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-frequency", &val))
> +			clk_set_rate(drvdata->onecell.clks[num], val);
> +	}
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	const struct of_device_id *match;
> +
> +	if (np == NULL)
> +		return -EINVAL;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	drvdata->variant = (enum si5351_variant)match->data;
> +	drvdata->pxtal = of_clk_get(np, 0);
> +	drvdata->pclkin = of_clk_get(np, 1);
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +			       GFP_KERNEL);
> +	if (drvdata == NULL) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	ret = si5351_dt_parse(client, drvdata);
> +	if (ret)
> +		return ret;
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		init.parent_names = &drvdata->pxtal->name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			init.parent_names = &drvdata->pclkin->name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->clkout = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	/* setup clock setup from DT */
> +	si5351_dt_setup(client, drvdata);
> +
> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +			    &drvdata->onecell);
> +
> +	dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +	i2c_set_clientdata(client, NULL);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = si5351_dt_ids,
> +	},
> +	.probe = si5351_i2c_probe,
> +	.remove = si5351_i2c_remove,
> +	.id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +	return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +	i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_MASK			(3<<0)
> +#define  SI5351_CLK_DRIVE_2MA			(0<<0)
> +#define  SI5351_CLK_DRIVE_4MA			(1<<0)
> +#define  SI5351_CLK_DRIVE_6MA			(2<<0)
> +#define  SI5351_CLK_DRIVE_8MA			(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> 


^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-19 19:15   ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-02-19 19:15 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Mike Turquette, Russell King - ARM Linux,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Rabeeh Khoury,
	Dom Cobley, Stephen Warren, Andrew Morton,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

Hi Sebastian,

I did some more tests today and it took me a while to dig for the root
cause why things were not working for me in the first place - see below.


On 09.02.2013 13:59, Sebastian Hesselbarth wrote:

> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator@60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;

As referred to in another thread, registering the ref25M clock that way
didn't suffice for me on my platform - but that's a different story.

> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +	unsigned char reg, struct si5351_parameters *params)
> +{
> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];

On a general note, I think you can use u8 instead of unsigned char all
over the place here, which will save you some indentation trouble.

> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				  unsigned char num, unsigned char parent)
> +{
> +	struct clk *pclk;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case 0:
> +		pclk = drvdata->msynth[num].hw.clk;
> +		break;
> +	case 1:
> +		pclk = drvdata->msynth[0].hw.clk;
> +		if (num >= 4)
> +			pclk = drvdata->msynth[4].hw.clk;
> +		break;
> +	case 2:
> +		pclk = drvdata->xtal.clk;
> +		break;
> +	case 3:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +		pclk = drvdata->clkin.clk;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}

[...]

> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned val;
> +
> +	val = 0;
> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +	switch (index) {
> +	case 0:
> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;

I fugured that _si5351_clkout_reparent() wouldn't actually call
->set_parent() on the clock, which leads to the fact that
CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
end leaf is actually supplied with a new rate, which leads to incorrect
effective clocks, depending on the current multisynth/pll configuration.

The reason for this is in clk_set_parent() itself, which bails if the
parent is already set to the passed value:

	if (clk->parent == parent)
		goto out;

I fixed that for now by explicitly setting the clock's parent to NULL
before calling clk_set_parent() in _si5351_clkout_reparent(), so the
calbacks are triggered. But there might be a nicer way, for example to
factor out the CLK_SET_RATE_PARENT handling to some function called from
_si5351_clkout_reparent() or so.

Anyway, with this hack in place along with the other details I mentioned
in my first mail, the driver seems to work for me now, which is great. I
will do more extensive tests later that week when I have access to
better scopes ...


Many thanks again,
Daniel



> +	case 1:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (hwdata->num == 0 || hwdata->num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case 2:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case 3:
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	}
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INPUT_MASK, val);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned char rdiv;
> +
> +	rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +		rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* powerdown clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.is_enabled = si5351_clkout_is_enabled,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	struct property *prop;
> +	const __be32 *p;
> +	unsigned int num, val;
> +
> +	if (np == NULL)
> +		return;
> +
> +	/*
> +	 * property pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(client->dev.of_node, "pll-source",
> +				 prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			break;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p)
> +			break;
> +
> +		if (_si5351_pll_reparent(drvdata, num, val))
> +			dev_warn(&client->dev,
> +				 "unable to reparent pll %d to %d\n",
> +				 num, val);
> +	}
> +
> +	for_each_child_of_node(client->dev.of_node, np) {
> +		if (of_property_read_u32(np, "reg", &num)) {
> +			dev_err(&client->dev, "missing reg property of %s\n",
> +				np->full_name);
> +			continue;
> +		}
> +
> +		if (of_property_read_bool(np, "pll-master"))
> +			_si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +		if (!of_property_read_u32(np, "drive-strength", &val)) {
> +			if (_si5351_clkout_set_drive_strength(drvdata,
> +							      num, val))
> +				dev_warn(&client->dev,
> +					 "unable to set drive strength of %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +			if (_si5351_msynth_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent multisynth %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-source", &val)) {
> +			if (_si5351_clkout_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent clockout %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-frequency", &val))
> +			clk_set_rate(drvdata->onecell.clks[num], val);
> +	}
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	const struct of_device_id *match;
> +
> +	if (np == NULL)
> +		return -EINVAL;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	drvdata->variant = (enum si5351_variant)match->data;
> +	drvdata->pxtal = of_clk_get(np, 0);
> +	drvdata->pclkin = of_clk_get(np, 1);
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +			       GFP_KERNEL);
> +	if (drvdata == NULL) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	ret = si5351_dt_parse(client, drvdata);
> +	if (ret)
> +		return ret;
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		init.parent_names = &drvdata->pxtal->name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			init.parent_names = &drvdata->pclkin->name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->clkout = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	/* setup clock setup from DT */
> +	si5351_dt_setup(client, drvdata);
> +
> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +			    &drvdata->onecell);
> +
> +	dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +	i2c_set_clientdata(client, NULL);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = si5351_dt_ids,
> +	},
> +	.probe = si5351_i2c_probe,
> +	.remove = si5351_i2c_remove,
> +	.id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +	return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +	i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth-EimvkypIwmU@public.gmane.org");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + * Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
> + *
> + * This program is free software; you can redistribute  it and/or modify it
> + * under  the terms of  the GNU General  Public License as published by the
> + * Free Software Foundation;  either version 2 of the  License, or (at your
> + * option) any later version.
> + */
> +
> +#ifndef _CLK_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_MASK			(3<<0)
> +#define  SI5351_CLK_DRIVE_2MA			(0<<0)
> +#define  SI5351_CLK_DRIVE_4MA			(1<<0)
> +#define  SI5351_CLK_DRIVE_6MA			(2<<0)
> +#define  SI5351_CLK_DRIVE_8MA			(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-19 19:15   ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-02-19 19:15 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sebastian,

I did some more tests today and it took me a while to dig for the root
cause why things were not working for me in the first place - see below.


On 09.02.2013 13:59, Sebastian Hesselbarth wrote:

> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator at 60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;

As referred to in another thread, registering the ref25M clock that way
didn't suffice for me on my platform - but that's a different story.

> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +	unsigned char reg, struct si5351_parameters *params)
> +{
> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];

On a general note, I think you can use u8 instead of unsigned char all
over the place here, which will save you some indentation trouble.

> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				  unsigned char num, unsigned char parent)
> +{
> +	struct clk *pclk;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case 0:
> +		pclk = drvdata->msynth[num].hw.clk;
> +		break;
> +	case 1:
> +		pclk = drvdata->msynth[0].hw.clk;
> +		if (num >= 4)
> +			pclk = drvdata->msynth[4].hw.clk;
> +		break;
> +	case 2:
> +		pclk = drvdata->xtal.clk;
> +		break;
> +	case 3:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +		pclk = drvdata->clkin.clk;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}

[...]

> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned val;
> +
> +	val = 0;
> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +	switch (index) {
> +	case 0:
> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;

I fugured that _si5351_clkout_reparent() wouldn't actually call
->set_parent() on the clock, which leads to the fact that
CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
end leaf is actually supplied with a new rate, which leads to incorrect
effective clocks, depending on the current multisynth/pll configuration.

The reason for this is in clk_set_parent() itself, which bails if the
parent is already set to the passed value:

	if (clk->parent == parent)
		goto out;

I fixed that for now by explicitly setting the clock's parent to NULL
before calling clk_set_parent() in _si5351_clkout_reparent(), so the
calbacks are triggered. But there might be a nicer way, for example to
factor out the CLK_SET_RATE_PARENT handling to some function called from
_si5351_clkout_reparent() or so.

Anyway, with this hack in place along with the other details I mentioned
in my first mail, the driver seems to work for me now, which is great. I
will do more extensive tests later that week when I have access to
better scopes ...


Many thanks again,
Daniel



> +	case 1:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (hwdata->num == 0 || hwdata->num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case 2:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case 3:
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	}
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INPUT_MASK, val);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned char rdiv;
> +
> +	rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +		rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* powerdown clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.is_enabled = si5351_clkout_is_enabled,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	struct property *prop;
> +	const __be32 *p;
> +	unsigned int num, val;
> +
> +	if (np == NULL)
> +		return;
> +
> +	/*
> +	 * property pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(client->dev.of_node, "pll-source",
> +				 prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			break;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p)
> +			break;
> +
> +		if (_si5351_pll_reparent(drvdata, num, val))
> +			dev_warn(&client->dev,
> +				 "unable to reparent pll %d to %d\n",
> +				 num, val);
> +	}
> +
> +	for_each_child_of_node(client->dev.of_node, np) {
> +		if (of_property_read_u32(np, "reg", &num)) {
> +			dev_err(&client->dev, "missing reg property of %s\n",
> +				np->full_name);
> +			continue;
> +		}
> +
> +		if (of_property_read_bool(np, "pll-master"))
> +			_si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +		if (!of_property_read_u32(np, "drive-strength", &val)) {
> +			if (_si5351_clkout_set_drive_strength(drvdata,
> +							      num, val))
> +				dev_warn(&client->dev,
> +					 "unable to set drive strength of %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +			if (_si5351_msynth_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent multisynth %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-source", &val)) {
> +			if (_si5351_clkout_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent clockout %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-frequency", &val))
> +			clk_set_rate(drvdata->onecell.clks[num], val);
> +	}
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	const struct of_device_id *match;
> +
> +	if (np == NULL)
> +		return -EINVAL;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	drvdata->variant = (enum si5351_variant)match->data;
> +	drvdata->pxtal = of_clk_get(np, 0);
> +	drvdata->pclkin = of_clk_get(np, 1);
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +			       GFP_KERNEL);
> +	if (drvdata == NULL) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	ret = si5351_dt_parse(client, drvdata);
> +	if (ret)
> +		return ret;
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		init.parent_names = &drvdata->pxtal->name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			init.parent_names = &drvdata->pclkin->name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->clkout = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	/* setup clock setup from DT */
> +	si5351_dt_setup(client, drvdata);
> +
> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +			    &drvdata->onecell);
> +
> +	dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +	i2c_set_clientdata(client, NULL);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = si5351_dt_ids,
> +	},
> +	.probe = si5351_i2c_probe,
> +	.remove = si5351_i2c_remove,
> +	.id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +	return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +	i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_MASK			(3<<0)
> +#define  SI5351_CLK_DRIVE_2MA			(0<<0)
> +#define  SI5351_CLK_DRIVE_4MA			(1<<0)
> +#define  SI5351_CLK_DRIVE_6MA			(2<<0)
> +#define  SI5351_CLK_DRIVE_8MA			(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
  2013-02-19 19:15   ` Daniel Mack
@ 2013-02-27 10:01     ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-02-27 10:01 UTC (permalink / raw)
  To: Daniel Mack
  Cc: Stephen Warren, linux-doc, linux-kernel, Rob Herring,
	Russell King - ARM Linux, Dom Cobley, Mike Turquette,
	Andrew Morton, Rabeeh Khoury, devicetree-discuss,
	linux-arm-kernel

Daniel,

first of all sorry for the late answer but thanks for testing the driver.

On 2/19/13, Daniel Mack <zonque@gmail.com> wrote:
> Hi Sebastian,
>
> I did some more tests today and it took me a while to dig for the root
> cause why things were not working for me in the first place - see below.
>
>
> On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
>
>> +==Example==
>> +
>> +/* 25MHz reference crystal */
>> +ref25: ref25M {
>> +	compatible = "fixed-clock";
>> +	#clock-cells = <0>;
>> +	clock-frequency = <25000000>;
>> +};
>> +
>> +i2c-master-node {
>> +
>> +	/* Si5351a msop10 i2c clock generator */
>> +	si5351a: clock-generator@60 {
>> +		compatible = "silabs,si5351a-msop";
>> +		reg = <0x60>;
>> +		#address-cells = <1>;
>> +		#size-cells = <0>;
>> +		#clock-cells = <1>;
>> +
>> +		/* connect xtal input to 25MHz reference */
>> +		clocks = <&ref25>;
>
> As referred to in another thread, registering the ref25M clock that way
> didn't suffice for me on my platform - but that's a different story.

I guess "fixed-clock" isn't registered by OMAP's clock init code? I had
to do this on dove, too. Actually, I will come back to clock initialization for
dove later and was hoping that there will be some global way of registering
core common clock drivers (or at least fixed-clock) until then.

>> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
>> +	unsigned char reg, struct si5351_parameters *params)
>> +{
>> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
>
> On a general note, I think you can use u8 instead of unsigned char all
> over the place here, which will save you some indentation trouble.

Ok, I guess I was deriving "unsigned char" usage from other clock drivers
and never went to u8. But I ll reconsider using u8 when all issues are
worked out.

>> +static inline int _si5351_clkout_reparent(struct si5351_driver_data
>> *drvdata,
>> +				  unsigned char num, unsigned char parent)
>> +{
>> +	struct clk *pclk;
>> +
>> +	if (num > 8 ||
>> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
>> +		return -EINVAL;
>> +
>> +	switch (parent) {
>> +	case 0:
>> +		pclk = drvdata->msynth[num].hw.clk;
>> +		break;
>> +	case 1:
>> +		pclk = drvdata->msynth[0].hw.clk;
>> +		if (num >= 4)
>> +			pclk = drvdata->msynth[4].hw.clk;
>> +		break;
>> +	case 2:
>> +		pclk = drvdata->xtal.clk;
>> +		break;
>> +	case 3:
>> +		if (drvdata->variant != SI5351_VARIANT_C)
>> +			return -EINVAL;
>> +		pclk = drvdata->clkin.clk;
>> +		break;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
>> +}
>
> [...]
>
>> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
>> +{
>> +	struct si5351_hw_data *hwdata =
>> +		container_of(hw, struct si5351_hw_data, hw);
>> +	unsigned val;
>> +
>> +	val = 0;
>> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
>> +	switch (index) {
>> +	case 0:
>> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
>> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
>> +		break;
>
> I fugured that _si5351_clkout_reparent() wouldn't actually call
> ->set_parent() on the clock, which leads to the fact that
> CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
> end leaf is actually supplied with a new rate, which leads to incorrect
> effective clocks, depending on the current multisynth/pll configuration.

Yeah, true. Unfortunately, _clkout_reparent() is more like a dirty hack to
allow to reparent the clock output. At registration internal configuration of
si5351 is not known and when I parse the DT for clock configuration I might
have been already assigned to the same parent clk that you later explicitly
configure.

What I basically want for si5351 (or any other eeprom based programmable
clock gen) is that stored configuration is not touched. But it can be changed
after eeprom contents have been copied into device's sram - and this _is_
mandatory for the si5351 that I use on CuBox where there is no useful config
stored at all.

Anyway, there still seem to be some more issues with doing it right on current
common clk framwork - thanks for pointing it out.

> The reason for this is in clk_set_parent() itself, which bails if the
> parent is already set to the passed value:
>
> 	if (clk->parent == parent)
> 		goto out;
>
> I fixed that for now by explicitly setting the clock's parent to NULL
> before calling clk_set_parent() in _si5351_clkout_reparent(), so the
> calbacks are triggered. But there might be a nicer way, for example to
> factor out the CLK_SET_RATE_PARENT handling to some function called from
> _si5351_clkout_reparent() or so.
>
> Anyway, with this hack in place along with the other details I mentioned
> in my first mail, the driver seems to work for me now, which is great. I
> will do more extensive tests later that week when I have access to
> better scopes ...

I am happy to hear that you can reproduce different frequencies successfully!

But as you already pointed out before, it would be great to have some long-term
jitter measurements. I hope the heuristic taken from the referenced Silicon Labs
application note will still meet jitter requirements. Still, as stated
on my initial post,
the pll settings obtained for different frequencies differ from that
the provided
windows tool spits out.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-02-27 10:01     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-02-27 10:01 UTC (permalink / raw)
  To: linux-arm-kernel

Daniel,

first of all sorry for the late answer but thanks for testing the driver.

On 2/19/13, Daniel Mack <zonque@gmail.com> wrote:
> Hi Sebastian,
>
> I did some more tests today and it took me a while to dig for the root
> cause why things were not working for me in the first place - see below.
>
>
> On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
>
>> +==Example==
>> +
>> +/* 25MHz reference crystal */
>> +ref25: ref25M {
>> +	compatible = "fixed-clock";
>> +	#clock-cells = <0>;
>> +	clock-frequency = <25000000>;
>> +};
>> +
>> +i2c-master-node {
>> +
>> +	/* Si5351a msop10 i2c clock generator */
>> +	si5351a: clock-generator at 60 {
>> +		compatible = "silabs,si5351a-msop";
>> +		reg = <0x60>;
>> +		#address-cells = <1>;
>> +		#size-cells = <0>;
>> +		#clock-cells = <1>;
>> +
>> +		/* connect xtal input to 25MHz reference */
>> +		clocks = <&ref25>;
>
> As referred to in another thread, registering the ref25M clock that way
> didn't suffice for me on my platform - but that's a different story.

I guess "fixed-clock" isn't registered by OMAP's clock init code? I had
to do this on dove, too. Actually, I will come back to clock initialization for
dove later and was hoping that there will be some global way of registering
core common clock drivers (or at least fixed-clock) until then.

>> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
>> +	unsigned char reg, struct si5351_parameters *params)
>> +{
>> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
>
> On a general note, I think you can use u8 instead of unsigned char all
> over the place here, which will save you some indentation trouble.

Ok, I guess I was deriving "unsigned char" usage from other clock drivers
and never went to u8. But I ll reconsider using u8 when all issues are
worked out.

>> +static inline int _si5351_clkout_reparent(struct si5351_driver_data
>> *drvdata,
>> +				  unsigned char num, unsigned char parent)
>> +{
>> +	struct clk *pclk;
>> +
>> +	if (num > 8 ||
>> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
>> +		return -EINVAL;
>> +
>> +	switch (parent) {
>> +	case 0:
>> +		pclk = drvdata->msynth[num].hw.clk;
>> +		break;
>> +	case 1:
>> +		pclk = drvdata->msynth[0].hw.clk;
>> +		if (num >= 4)
>> +			pclk = drvdata->msynth[4].hw.clk;
>> +		break;
>> +	case 2:
>> +		pclk = drvdata->xtal.clk;
>> +		break;
>> +	case 3:
>> +		if (drvdata->variant != SI5351_VARIANT_C)
>> +			return -EINVAL;
>> +		pclk = drvdata->clkin.clk;
>> +		break;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
>> +}
>
> [...]
>
>> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
>> +{
>> +	struct si5351_hw_data *hwdata =
>> +		container_of(hw, struct si5351_hw_data, hw);
>> +	unsigned val;
>> +
>> +	val = 0;
>> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
>> +	switch (index) {
>> +	case 0:
>> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
>> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
>> +		break;
>
> I fugured that _si5351_clkout_reparent() wouldn't actually call
> ->set_parent() on the clock, which leads to the fact that
> CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
> end leaf is actually supplied with a new rate, which leads to incorrect
> effective clocks, depending on the current multisynth/pll configuration.

Yeah, true. Unfortunately, _clkout_reparent() is more like a dirty hack to
allow to reparent the clock output. At registration internal configuration of
si5351 is not known and when I parse the DT for clock configuration I might
have been already assigned to the same parent clk that you later explicitly
configure.

What I basically want for si5351 (or any other eeprom based programmable
clock gen) is that stored configuration is not touched. But it can be changed
after eeprom contents have been copied into device's sram - and this _is_
mandatory for the si5351 that I use on CuBox where there is no useful config
stored at all.

Anyway, there still seem to be some more issues with doing it right on current
common clk framwork - thanks for pointing it out.

> The reason for this is in clk_set_parent() itself, which bails if the
> parent is already set to the passed value:
>
> 	if (clk->parent == parent)
> 		goto out;
>
> I fixed that for now by explicitly setting the clock's parent to NULL
> before calling clk_set_parent() in _si5351_clkout_reparent(), so the
> calbacks are triggered. But there might be a nicer way, for example to
> factor out the CLK_SET_RATE_PARENT handling to some function called from
> _si5351_clkout_reparent() or so.
>
> Anyway, with this hack in place along with the other details I mentioned
> in my first mail, the driver seems to work for me now, which is great. I
> will do more extensive tests later that week when I have access to
> better scopes ...

I am happy to hear that you can reproduce different frequencies successfully!

But as you already pointed out before, it would be great to have some long-term
jitter measurements. I hope the heuristic taken from the referenced Silicon Labs
application note will still meet jitter requirements. Still, as stated
on my initial post,
the pll settings obtained for different frequencies differ from that
the provided
windows tool spits out.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] clk: add si5351 i2c common clock driver
  2013-02-27 10:01     ` Sebastian Hesselbarth
@ 2013-03-01 15:01       ` Daniel Mack
  -1 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-03-01 15:01 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Stephen Warren, linux-doc, linux-kernel, Rob Herring,
	Russell King - ARM Linux, Dom Cobley, Mike Turquette,
	Andrew Morton, Rabeeh Khoury, devicetree-discuss,
	linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 4946 bytes --]

Hi Sebastian,

On 27.02.2013 11:01, Sebastian Hesselbarth wrote:
> first of all sorry for the late answer but thanks for testing the driver.
> 
> On 2/19/13, Daniel Mack <zonque@gmail.com> wrote:
>> Hi Sebastian,
>>
>> I did some more tests today and it took me a while to dig for the root
>> cause why things were not working for me in the first place - see below.
>>
>>
>> On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
>>
>>> +==Example==
>>> +
>>> +/* 25MHz reference crystal */
>>> +ref25: ref25M {
>>> +	compatible = "fixed-clock";
>>> +	#clock-cells = <0>;
>>> +	clock-frequency = <25000000>;
>>> +};
>>> +
>>> +i2c-master-node {
>>> +
>>> +	/* Si5351a msop10 i2c clock generator */
>>> +	si5351a: clock-generator@60 {
>>> +		compatible = "silabs,si5351a-msop";
>>> +		reg = <0x60>;
>>> +		#address-cells = <1>;
>>> +		#size-cells = <0>;
>>> +		#clock-cells = <1>;
>>> +
>>> +		/* connect xtal input to 25MHz reference */
>>> +		clocks = <&ref25>;
>>
>> As referred to in another thread, registering the ref25M clock that way
>> didn't suffice for me on my platform - but that's a different story.
> 
> I guess "fixed-clock" isn't registered by OMAP's clock init code? I had
> to do this on dove, too. Actually, I will come back to clock initialization for
> dove later and was hoping that there will be some global way of registering
> core common clock drivers (or at least fixed-clock) until then.
> 
>>> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
>>> +	unsigned char reg, struct si5351_parameters *params)
>>> +{
>>> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
>>
>> On a general note, I think you can use u8 instead of unsigned char all
>> over the place here, which will save you some indentation trouble.
> 
> Ok, I guess I was deriving "unsigned char" usage from other clock drivers
> and never went to u8. But I ll reconsider using u8 when all issues are
> worked out.
> 
>>> +static inline int _si5351_clkout_reparent(struct si5351_driver_data
>>> *drvdata,
>>> +				  unsigned char num, unsigned char parent)
>>> +{
>>> +	struct clk *pclk;
>>> +
>>> +	if (num > 8 ||
>>> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
>>> +		return -EINVAL;
>>> +
>>> +	switch (parent) {
>>> +	case 0:
>>> +		pclk = drvdata->msynth[num].hw.clk;
>>> +		break;
>>> +	case 1:
>>> +		pclk = drvdata->msynth[0].hw.clk;
>>> +		if (num >= 4)
>>> +			pclk = drvdata->msynth[4].hw.clk;
>>> +		break;
>>> +	case 2:
>>> +		pclk = drvdata->xtal.clk;
>>> +		break;
>>> +	case 3:
>>> +		if (drvdata->variant != SI5351_VARIANT_C)
>>> +			return -EINVAL;
>>> +		pclk = drvdata->clkin.clk;
>>> +		break;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
>>> +}
>>
>> [...]
>>
>>> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
>>> +{
>>> +	struct si5351_hw_data *hwdata =
>>> +		container_of(hw, struct si5351_hw_data, hw);
>>> +	unsigned val;
>>> +
>>> +	val = 0;
>>> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
>>> +	switch (index) {
>>> +	case 0:
>>> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
>>> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
>>> +		break;
>>
>> I fugured that _si5351_clkout_reparent() wouldn't actually call
>> ->set_parent() on the clock, which leads to the fact that
>> CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
>> end leaf is actually supplied with a new rate, which leads to incorrect
>> effective clocks, depending on the current multisynth/pll configuration.
> 
> Yeah, true. Unfortunately, _clkout_reparent() is more like a dirty hack to
> allow to reparent the clock output. At registration internal configuration of
> si5351 is not known and when I parse the DT for clock configuration I might
> have been already assigned to the same parent clk that you later explicitly
> configure.

Hmm, but then we need to either set the configuration on the chip to
match the internal state of the driver, or tell the clock framework that
the current state is not currently known (don't know if that's possible).

> What I basically want for si5351 (or any other eeprom based programmable
> clock gen) is that stored configuration is not touched. But it can be changed
> after eeprom contents have been copied into device's sram - and this _is_
> mandatory for the si5351 that I use on CuBox where there is no useful config
> stored at all.

Yes, same here. We set up the si5351 from the bootloader with some
binary blob via i2c to bring some components to live before the kernel
boots.

> Anyway, there still seem to be some more issues with doing it right on current
> common clk framwork - thanks for pointing it out.

I'd be happy to test more versions if you have any.

FWIW, attached is a patch for caching the 'prepared' states of the
clocks. This fixes the 'scheduling while atomic' bug I described earlier
in this thread. You can squash it into your patch if you want.



Thanks,
Daniel

[-- Attachment #2: 0001-si5351-cache-prepared-bits-of-clocks.patch --]
[-- Type: text/x-patch, Size: 4908 bytes --]

>From 3e7a1a8e1f3e472e473e5ab47329bb44a9ec24ba Mon Sep 17 00:00:00 2001
From: Daniel Mack <zonque@gmail.com>
Date: Sun, 17 Feb 2013 18:01:08 +0100
Subject: [PATCH] si5351: cache 'prepared' bits of clocks

---
 drivers/clk/clk-si5351.c | 47 ++++++++++++++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 13 deletions(-)

diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
index b526742..528f88a 100644
--- a/drivers/clk/clk-si5351.c
+++ b/drivers/clk/clk-si5351.c
@@ -54,6 +54,7 @@ struct si5351_hw_data {
 	struct si5351_driver_data	*drvdata;
 	struct si5351_parameters	params;
 	unsigned char			num;
+	bool				prepared;
 };
 
 struct si5351_driver_data {
@@ -70,6 +71,9 @@ struct si5351_driver_data {
 	struct si5351_hw_data	pll[2];
 	struct si5351_hw_data	*msynth;
 	struct si5351_hw_data	*clkout;
+
+	bool			xtal_prepared;
+	bool			clkin_prepared;
 };
 
 static const char const *si5351_input_names[] = {
@@ -223,6 +227,8 @@ static int si5351_xtal_prepare(struct clk_hw *hw)
 		container_of(hw, struct si5351_driver_data, xtal);
 	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
 			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	drvdata->xtal_prepared  = true;
+
 	return 0;
 }
 
@@ -232,15 +238,14 @@ static void si5351_xtal_unprepare(struct clk_hw *hw)
 		container_of(hw, struct si5351_driver_data, xtal);
 	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
 			SI5351_XTAL_ENABLE, 0);
+	drvdata->xtal_prepared  = false;
 }
 
 static int si5351_xtal_is_enabled(struct clk_hw *hw)
 {
 	struct si5351_driver_data *drvdata =
 		container_of(hw, struct si5351_driver_data, xtal);
-	unsigned char reg;
-	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
-	return (reg & SI5351_XTAL_ENABLE) ? 1 : 0;
+	return drvdata->xtal_prepared;
 }
 
 static const struct clk_ops si5351_xtal_ops = {
@@ -258,7 +263,8 @@ static int si5351_clkin_prepare(struct clk_hw *hw)
 		container_of(hw, struct si5351_driver_data, clkin);
 	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
 			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
-	return 0;
+	drvdata->clkin_prepared  = true;
+	return 1;
 }
 
 static void si5351_clkin_unprepare(struct clk_hw *hw)
@@ -267,15 +273,14 @@ static void si5351_clkin_unprepare(struct clk_hw *hw)
 		container_of(hw, struct si5351_driver_data, clkin);
 	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
 			SI5351_CLKIN_ENABLE, 0);
+	drvdata->clkin_prepared  = false;
 }
 
 static int si5351_clkin_is_enabled(struct clk_hw *hw)
 {
 	struct si5351_driver_data *drvdata =
 		container_of(hw, struct si5351_driver_data, clkin);
-	unsigned char reg;
-	reg = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
-	return (reg & SI5351_CLKIN_ENABLE) ? 1 : 0;
+	return drvdata->clkin_prepared;
 }
 
 /*
@@ -660,6 +665,7 @@ static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
 		m += hwdata->params.p2;
 		m += 512 * hwdata->params.p3;
 	}
+printk(" XXXX m %d (%d)\n", m, hwdata->num);
 	do_div(rate, m);
 
 	dev_dbg(&hwdata->drvdata->client->dev,
@@ -887,6 +893,8 @@ static int si5351_clkout_prepare(struct clk_hw *hw)
 			SI5351_CLK_POWERDOWN, 0);
 	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
 			(1 << hwdata->num), 0);
+	hwdata->prepared = true;
+
 	return 0;
 }
 
@@ -899,21 +907,28 @@ static void si5351_clkout_unprepare(struct clk_hw *hw)
 			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
 	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
 			(1 << hwdata->num), (1 << hwdata->num));
+	hwdata->prepared = false;
 }
 
-static int si5351_clkout_is_enabled(struct clk_hw *hw)
+static void __si5351_read_clkout_prepared(struct si5351_hw_data *hwdata)
 {
-	struct si5351_hw_data *hwdata =
-		container_of(hw, struct si5351_hw_data, hw);
 	unsigned char val;
 
 	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
 	if (val & SI5351_CLK_POWERDOWN)
-		return 0;
+		hwdata->prepared = false;
 	val = si5351_reg_read(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL);
 	if (val & (1 << hwdata->num))
-		return 0;
-	return 1;
+		hwdata->prepared = false;
+	hwdata->prepared = true;
+}
+
+static int si5351_clkout_is_enabled(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	return hwdata->prepared;
 }
 
 static u8 si5351_clkout_get_parent(struct clk_hw *hw)
@@ -1306,6 +1321,11 @@ static int si5351_i2c_probe(
 		return -EINVAL;
 	}
 
+	/* read current clock enable bits */
+	ret = si5351_reg_read(drvdata, SI5351_FANOUT_ENABLE);
+	drvdata->xtal_prepared = !!(ret & SI5351_XTAL_ENABLE);
+	drvdata->clkin_prepared  = !!(ret & SI5351_CLKIN_ENABLE);
+
 	/* register PLLB or VXCO (Si5351B) */
 	drvdata->pll[1].num = 1;
 	drvdata->pll[1].drvdata = drvdata;
@@ -1394,6 +1414,7 @@ static int si5351_i2c_probe(
 			return -EINVAL;
 		}
 		drvdata->onecell.clks[n] = clk;
+		__si5351_read_clkout_prepared(&drvdata->clkout[n]);
 	}
 
 	/* setup clock setup from DT */
-- 
1.8.1.2


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH] clk: add si5351 i2c common clock driver
@ 2013-03-01 15:01       ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-03-01 15:01 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sebastian,

On 27.02.2013 11:01, Sebastian Hesselbarth wrote:
> first of all sorry for the late answer but thanks for testing the driver.
> 
> On 2/19/13, Daniel Mack <zonque@gmail.com> wrote:
>> Hi Sebastian,
>>
>> I did some more tests today and it took me a while to dig for the root
>> cause why things were not working for me in the first place - see below.
>>
>>
>> On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
>>
>>> +==Example==
>>> +
>>> +/* 25MHz reference crystal */
>>> +ref25: ref25M {
>>> +	compatible = "fixed-clock";
>>> +	#clock-cells = <0>;
>>> +	clock-frequency = <25000000>;
>>> +};
>>> +
>>> +i2c-master-node {
>>> +
>>> +	/* Si5351a msop10 i2c clock generator */
>>> +	si5351a: clock-generator at 60 {
>>> +		compatible = "silabs,si5351a-msop";
>>> +		reg = <0x60>;
>>> +		#address-cells = <1>;
>>> +		#size-cells = <0>;
>>> +		#clock-cells = <1>;
>>> +
>>> +		/* connect xtal input to 25MHz reference */
>>> +		clocks = <&ref25>;
>>
>> As referred to in another thread, registering the ref25M clock that way
>> didn't suffice for me on my platform - but that's a different story.
> 
> I guess "fixed-clock" isn't registered by OMAP's clock init code? I had
> to do this on dove, too. Actually, I will come back to clock initialization for
> dove later and was hoping that there will be some global way of registering
> core common clock drivers (or at least fixed-clock) until then.
> 
>>> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
>>> +	unsigned char reg, struct si5351_parameters *params)
>>> +{
>>> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
>>
>> On a general note, I think you can use u8 instead of unsigned char all
>> over the place here, which will save you some indentation trouble.
> 
> Ok, I guess I was deriving "unsigned char" usage from other clock drivers
> and never went to u8. But I ll reconsider using u8 when all issues are
> worked out.
> 
>>> +static inline int _si5351_clkout_reparent(struct si5351_driver_data
>>> *drvdata,
>>> +				  unsigned char num, unsigned char parent)
>>> +{
>>> +	struct clk *pclk;
>>> +
>>> +	if (num > 8 ||
>>> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
>>> +		return -EINVAL;
>>> +
>>> +	switch (parent) {
>>> +	case 0:
>>> +		pclk = drvdata->msynth[num].hw.clk;
>>> +		break;
>>> +	case 1:
>>> +		pclk = drvdata->msynth[0].hw.clk;
>>> +		if (num >= 4)
>>> +			pclk = drvdata->msynth[4].hw.clk;
>>> +		break;
>>> +	case 2:
>>> +		pclk = drvdata->xtal.clk;
>>> +		break;
>>> +	case 3:
>>> +		if (drvdata->variant != SI5351_VARIANT_C)
>>> +			return -EINVAL;
>>> +		pclk = drvdata->clkin.clk;
>>> +		break;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
>>> +}
>>
>> [...]
>>
>>> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
>>> +{
>>> +	struct si5351_hw_data *hwdata =
>>> +		container_of(hw, struct si5351_hw_data, hw);
>>> +	unsigned val;
>>> +
>>> +	val = 0;
>>> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
>>> +	switch (index) {
>>> +	case 0:
>>> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
>>> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
>>> +		break;
>>
>> I fugured that _si5351_clkout_reparent() wouldn't actually call
>> ->set_parent() on the clock, which leads to the fact that
>> CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
>> end leaf is actually supplied with a new rate, which leads to incorrect
>> effective clocks, depending on the current multisynth/pll configuration.
> 
> Yeah, true. Unfortunately, _clkout_reparent() is more like a dirty hack to
> allow to reparent the clock output. At registration internal configuration of
> si5351 is not known and when I parse the DT for clock configuration I might
> have been already assigned to the same parent clk that you later explicitly
> configure.

Hmm, but then we need to either set the configuration on the chip to
match the internal state of the driver, or tell the clock framework that
the current state is not currently known (don't know if that's possible).

> What I basically want for si5351 (or any other eeprom based programmable
> clock gen) is that stored configuration is not touched. But it can be changed
> after eeprom contents have been copied into device's sram - and this _is_
> mandatory for the si5351 that I use on CuBox where there is no useful config
> stored at all.

Yes, same here. We set up the si5351 from the bootloader with some
binary blob via i2c to bring some components to live before the kernel
boots.

> Anyway, there still seem to be some more issues with doing it right on current
> common clk framwork - thanks for pointing it out.

I'd be happy to test more versions if you have any.

FWIW, attached is a patch for caching the 'prepared' states of the
clocks. This fixes the 'scheduling while atomic' bug I described earlier
in this thread. You can squash it into your patch if you want.



Thanks,
Daniel
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-si5351-cache-prepared-bits-of-clocks.patch
Type: text/x-patch
Size: 4908 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20130301/a0e2ee23/attachment.bin>

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v2] clk: add si5351 i2c common clock driver
  2013-02-09 12:59 ` Sebastian Hesselbarth
@ 2013-03-16 13:10   ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-16 13:10 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1413 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1693 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..f73d2d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			drive-strength = <8>;
+			multisynth-source = <0>;
+			clock-source = <0>;
+			pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			drive-strength = <4>;
+			multisynth-source = <1>;
+			clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index e23e471..aba4f59 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 095899e..8f58889 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
 
 # Debug drivers
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..1844ae2
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1413 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else
+		idiv = SI5351_CLKIN_DIV_1;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+					    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX,
+					    &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5)
+		m = hwdata->params.p1;
+	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
+		m = 4;
+	else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned char rdiv;
+
+	rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
+		SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "multisynth-source", &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(
+	struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
+			       GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->clkout = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct clk *), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
+			    &drvdata->onecell);
+
+	dev_info(&client->dev, "registered si5351 i2c client\n");
+
+	return 0;
+}
+
+static int si5351_i2c_remove(struct i2c_client *client)
+{
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.remove = si5351_i2c_remove,
+	.id_table = si5351_i2c_ids,
+};
+
+static int __init si5351_module_init(void)
+{
+	return i2c_add_driver(&si5351_driver);
+}
+module_init(si5351_module_init);
+
+static void __exit si5351_module_exit(void)
+{
+	i2c_del_driver(&si5351_driver);
+}
+module_exit(si5351_module_exit);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v2] clk: add si5351 i2c common clock driver
@ 2013-03-16 13:10   ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-16 13:10 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1413 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1693 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..f73d2d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			drive-strength = <8>;
+			multisynth-source = <0>;
+			clock-source = <0>;
+			pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			drive-strength = <4>;
+			multisynth-source = <1>;
+			clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index e23e471..aba4f59 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 095899e..8f58889 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
 
 # Debug drivers
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..1844ae2
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1413 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else
+		idiv = SI5351_CLKIN_DIV_1;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+					    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX,
+					    &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5)
+		m = hwdata->params.p1;
+	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
+		m = 4;
+	else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned char rdiv;
+
+	rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
+		SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = SI5351_CLK0_PARAMETERS +
+		(SI5351_PARAMETERS_LENGTH * hwdata->num);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "multisynth-source", &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(
+	struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
+			       GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->clkout = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct clk *), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
+			    &drvdata->onecell);
+
+	dev_info(&client->dev, "registered si5351 i2c client\n");
+
+	return 0;
+}
+
+static int si5351_i2c_remove(struct i2c_client *client)
+{
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.remove = si5351_i2c_remove,
+	.id_table = si5351_i2c_ids,
+};
+
+static int __init si5351_module_init(void)
+{
+	return i2c_add_driver(&si5351_driver);
+}
+module_init(si5351_module_init);
+
+static void __exit si5351_module_exit(void)
+{
+	i2c_del_driver(&si5351_driver);
+}
+module_exit(si5351_module_exit);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v2] clk: add si5351 i2c common clock driver
  2013-03-16 13:10   ` Sebastian Hesselbarth
@ 2013-03-16 15:10     ` Daniel Mack
  -1 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-03-16 15:10 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Jean-Francois Moine, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

Hi Sebastian,

On 16.03.2013 14:10, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1413 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1693 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h

[...]

> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5)
> +		m = hwdata->params.p1;
> +	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
> +		m = 4;
> +	else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}

A nit only, but according to Documentation/CodingStyle, all if/else
blocks need curly brackets if any of them needs them. Maybe there are
more places which are affected.

> +	do_div(rate, m);

I still have the problem that m == 0 in my case as I only set up two
clocks from my DT - which leads to a DIV0. The easiest fix is certainly
to bail here in that case.

Another option would be to only register the clocks to the framework
that are actually set up from DT, but that would require more rework at
the driver's probe level.

Other than that, this driver works perfectly for me - thanks again for
your work!




Daniel


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v2] clk: add si5351 i2c common clock driver
@ 2013-03-16 15:10     ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-03-16 15:10 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sebastian,

On 16.03.2013 14:10, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: devicetree-discuss at lists.ozlabs.org
> Cc: linux-doc at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-arm-kernel at lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1413 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1693 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h

[...]

> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = SI5351_CLK0_PARAMETERS +
> +		(SI5351_PARAMETERS_LENGTH * hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5)
> +		m = hwdata->params.p1;
> +	else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		  SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4)
> +		m = 4;
> +	else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}

A nit only, but according to Documentation/CodingStyle, all if/else
blocks need curly brackets if any of them needs them. Maybe there are
more places which are affected.

> +	do_div(rate, m);

I still have the problem that m == 0 in my case as I only set up two
clocks from my DT - which leads to a DIV0. The easiest fix is certainly
to bail here in that case.

Another option would be to only register the clocks to the framework
that are actually set up from DT, but that would require more rework at
the driver's probe level.

Other than that, this driver works perfectly for me - thanks again for
your work!




Daniel

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
  2013-02-09 12:59 ` Sebastian Hesselbarth
@ 2013-03-18 10:43   ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-18 10:43 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1709 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..f73d2d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			drive-strength = <8>;
+			multisynth-source = <0>;
+			clock-source = <0>;
+			pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			drive-strength = <4>;
+			multisynth-source = <1>;
+			clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index e23e471..aba4f59 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 095899e..8f58889 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
 
 # Debug drivers
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..38540e7
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1429 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline unsigned char si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "multisynth-source", &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(
+	struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
+			       GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->clkout = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct clk *), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
+			    &drvdata->onecell);
+
+	dev_info(&client->dev, "registered si5351 i2c client\n");
+
+	return 0;
+}
+
+static int si5351_i2c_remove(struct i2c_client *client)
+{
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.remove = si5351_i2c_remove,
+	.id_table = si5351_i2c_ids,
+};
+
+static int __init si5351_module_init(void)
+{
+	return i2c_add_driver(&si5351_driver);
+}
+module_init(si5351_module_init);
+
+static void __exit si5351_module_exit(void)
+{
+	i2c_del_driver(&si5351_driver);
+}
+module_exit(si5351_module_exit);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-18 10:43   ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-18 10:43 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1709 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..f73d2d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			drive-strength = <8>;
+			multisynth-source = <0>;
+			clock-source = <0>;
+			pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			drive-strength = <4>;
+			multisynth-source = <1>;
+			clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index e23e471..aba4f59 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 095899e..8f58889 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
 
 # Debug drivers
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..38540e7
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1429 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline unsigned char si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "multisynth-source", &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(
+	struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
+			       GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(struct clk_init_data));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->clkout = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(struct clk *), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(struct clk_init_data));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
+			    &drvdata->onecell);
+
+	dev_info(&client->dev, "registered si5351 i2c client\n");
+
+	return 0;
+}
+
+static int si5351_i2c_remove(struct i2c_client *client)
+{
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
+	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.remove = si5351_i2c_remove,
+	.id_table = si5351_i2c_ids,
+};
+
+static int __init si5351_module_init(void)
+{
+	return i2c_add_driver(&si5351_driver);
+}
+module_init(si5351_module_init);
+
+static void __exit si5351_module_exit(void)
+{
+	i2c_del_driver(&si5351_driver);
+}
+module_exit(si5351_module_exit);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] clk: add si5351 i2c common clock driver
  2013-03-18 10:43   ` Sebastian Hesselbarth
@ 2013-03-18 11:37     ` Daniel Mack
  -1 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-03-18 11:37 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Jean-Francois Moine, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On 18.03.2013 11:43, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>

Tested-by: Daniel Mack <zonque@gmail.com>


Thanks!

Daniel



> ---
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1709 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..f73d2d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator@60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			drive-strength = <8>;
> +			multisynth-source = <0>;
> +			clock-source = <0>;
> +			pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			drive-strength = <4>;
> +			multisynth-source = <1>;
> +			clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index e23e471..aba4f59 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 095899e..8f58889 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
>  
>  # Debug drivers
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..38540e7
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1429 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>
> +#include <linux/clkdev.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
> +	unsigned char reg)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(drvdata->regmap, reg, &val);
> +	if (ret) {
> +		dev_err(&drvdata->client->dev,
> +			"unable to read from reg%02x\n", reg);
> +		return 0;
> +	}
> +
> +	return (unsigned char)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char count, unsigned char *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char count, const unsigned char *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char mask, unsigned char val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline unsigned char si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +	unsigned char reg, struct si5351_parameters *params)
> +{
> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +	unsigned char reg, struct si5351_parameters *params)
> +{
> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				       unsigned char num, unsigned char parent)
> +{
> +	if (num > 2 ||
> +	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
> +		return -EINVAL;
> +
> +	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
> +			      drvdata->clkin.clk : drvdata->xtal.clk);
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char mask = (hwdata->num == 0) ?
> +		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char mask = (hwdata->num == 0) ?
> +		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
> +			mask, (index) ? 0 : mask);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = (hwdata->num == 0) ?
> +		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = (hwdata->num == 0) ?
> +		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +	unsigned char val;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	/* reset pll */
> +	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
> +	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static inline void _si5351_msynth_set_pll_master(
> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> +{
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return;
> +
> +	if (is_master)
> +		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +	else
> +		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +}
> +
> +static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				  unsigned char num, unsigned char parent)
> +{
> +	if (parent > 2)
> +		return -EINVAL;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	return clk_set_parent(drvdata->msynth[num].hw.clk,
> +			      drvdata->pll[parent].hw.clk);
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_PLL_SELECT,
> +			(index) ? SI5351_CLK_PLL_SELECT : 0);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
> +		rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
> +				     unsigned char num, unsigned char drive)
> +{
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case 2:
> +		drive = SI5351_CLK_DRIVE_2MA;
> +		break;
> +	case 4:
> +		drive = SI5351_CLK_DRIVE_4MA;
> +		break;
> +	case 6:
> +		drive = SI5351_CLK_DRIVE_6MA;
> +		break;
> +	case 8:
> +		drive = SI5351_CLK_DRIVE_8MA;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_MASK, drive);
> +
> +	return 0;
> +}
> +
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				  unsigned char num, unsigned char parent)
> +{
> +	struct clk *pclk;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +	switch (parent) {
> +	case 0:
> +		pclk = drvdata->msynth[num].hw.clk;
> +		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +		break;
> +	case 1:
> +		pclk = drvdata->msynth[0].hw.clk;
> +		if (num >= 4)
> +			pclk = drvdata->msynth[4].hw.clk;
> +		break;
> +	case 2:
> +		pclk = drvdata->xtal.clk;
> +		break;
> +	case 3:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +		pclk = drvdata->clkin.clk;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned val;
> +
> +	val = 0;
> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +	switch (index) {
> +	case 0:
> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case 1:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (hwdata->num == 0 || hwdata->num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case 2:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case 3:
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	}
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INPUT_MASK, val);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +		rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* powerdown clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	struct property *prop;
> +	const __be32 *p;
> +	unsigned int num, val;
> +
> +	if (np == NULL)
> +		return;
> +
> +	/*
> +	 * property pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(client->dev.of_node, "pll-source",
> +				 prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			break;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p)
> +			break;
> +
> +		if (_si5351_pll_reparent(drvdata, num, val))
> +			dev_warn(&client->dev,
> +				 "unable to reparent pll %d to %d\n",
> +				 num, val);
> +	}
> +
> +	for_each_child_of_node(client->dev.of_node, np) {
> +		if (of_property_read_u32(np, "reg", &num)) {
> +			dev_err(&client->dev, "missing reg property of %s\n",
> +				np->full_name);
> +			continue;
> +		}
> +
> +		if (of_property_read_bool(np, "pll-master"))
> +			_si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +		if (!of_property_read_u32(np, "drive-strength", &val)) {
> +			if (_si5351_clkout_set_drive_strength(drvdata,
> +							      num, val))
> +				dev_warn(&client->dev,
> +					 "unable to set drive strength of %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +			if (_si5351_msynth_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent multisynth %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-source", &val)) {
> +			if (_si5351_clkout_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent clockout %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-frequency", &val))
> +			clk_set_rate(drvdata->onecell.clks[num], val);
> +	}
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	const struct of_device_id *match;
> +
> +	if (np == NULL)
> +		return -EINVAL;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	drvdata->variant = (enum si5351_variant)match->data;
> +	drvdata->pxtal = of_clk_get(np, 0);
> +	drvdata->pclkin = of_clk_get(np, 1);
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +			       GFP_KERNEL);
> +	if (drvdata == NULL) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	ret = si5351_dt_parse(client, drvdata);
> +	if (ret)
> +		return ret;
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		init.parent_names = &drvdata->pxtal->name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			init.parent_names = &drvdata->pclkin->name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->clkout = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	/* setup clock setup from DT */
> +	si5351_dt_setup(client, drvdata);
> +
> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +			    &drvdata->onecell);
> +
> +	dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +	i2c_set_clientdata(client, NULL);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = si5351_dt_ids,
> +	},
> +	.probe = si5351_i2c_probe,
> +	.remove = si5351_i2c_remove,
> +	.id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +	return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +	i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_MASK			(3<<0)
> +#define  SI5351_CLK_DRIVE_2MA			(0<<0)
> +#define  SI5351_CLK_DRIVE_4MA			(1<<0)
> +#define  SI5351_CLK_DRIVE_6MA			(2<<0)
> +#define  SI5351_CLK_DRIVE_8MA			(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> 


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-18 11:37     ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-03-18 11:37 UTC (permalink / raw)
  To: linux-arm-kernel

On 18.03.2013 11:43, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>

Tested-by: Daniel Mack <zonque@gmail.com>


Thanks!

Daniel



> ---
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: devicetree-discuss at lists.ozlabs.org
> Cc: linux-doc at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-arm-kernel at lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1709 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..f73d2d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator at 60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			drive-strength = <8>;
> +			multisynth-source = <0>;
> +			clock-source = <0>;
> +			pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			drive-strength = <4>;
> +			multisynth-source = <1>;
> +			clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index e23e471..aba4f59 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 095899e..8f58889 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
>  
>  # Debug drivers
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..38540e7
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1429 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>
> +#include <linux/clkdev.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
> +	unsigned char reg)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(drvdata->regmap, reg, &val);
> +	if (ret) {
> +		dev_err(&drvdata->client->dev,
> +			"unable to read from reg%02x\n", reg);
> +		return 0;
> +	}
> +
> +	return (unsigned char)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char count, unsigned char *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char count, const unsigned char *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +	unsigned char reg, unsigned char mask, unsigned char val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline unsigned char si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +	unsigned char reg, struct si5351_parameters *params)
> +{
> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +	unsigned char reg, struct si5351_parameters *params)
> +{
> +	unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				       unsigned char num, unsigned char parent)
> +{
> +	if (num > 2 ||
> +	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
> +		return -EINVAL;
> +
> +	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
> +			      drvdata->clkin.clk : drvdata->xtal.clk);
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char mask = (hwdata->num == 0) ?
> +		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char mask = (hwdata->num == 0) ?
> +		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
> +			mask, (index) ? 0 : mask);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = (hwdata->num == 0) ?
> +		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = (hwdata->num == 0) ?
> +		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +	unsigned char val;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	/* reset pll */
> +	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
> +	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static inline void _si5351_msynth_set_pll_master(
> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> +{
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return;
> +
> +	if (is_master)
> +		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +	else
> +		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +}
> +
> +static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				  unsigned char num, unsigned char parent)
> +{
> +	if (parent > 2)
> +		return -EINVAL;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	return clk_set_parent(drvdata->msynth[num].hw.clk,
> +			      drvdata->pll[parent].hw.clk);
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_PLL_SELECT,
> +			(index) ? SI5351_CLK_PLL_SELECT : 0);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
> +		rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name,
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
> +				     unsigned char num, unsigned char drive)
> +{
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case 2:
> +		drive = SI5351_CLK_DRIVE_2MA;
> +		break;
> +	case 4:
> +		drive = SI5351_CLK_DRIVE_4MA;
> +		break;
> +	case 6:
> +		drive = SI5351_CLK_DRIVE_6MA;
> +		break;
> +	case 8:
> +		drive = SI5351_CLK_DRIVE_8MA;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_MASK, drive);
> +
> +	return 0;
> +}
> +
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				  unsigned char num, unsigned char parent)
> +{
> +	struct clk *pclk;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return -EINVAL;
> +
> +	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +	switch (parent) {
> +	case 0:
> +		pclk = drvdata->msynth[num].hw.clk;
> +		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +		break;
> +	case 1:
> +		pclk = drvdata->msynth[0].hw.clk;
> +		if (num >= 4)
> +			pclk = drvdata->msynth[4].hw.clk;
> +		break;
> +	case 2:
> +		pclk = drvdata->xtal.clk;
> +		break;
> +	case 3:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +		pclk = drvdata->clkin.clk;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned val;
> +
> +	val = 0;
> +	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +	switch (index) {
> +	case 0:
> +		hw->clk->flags |= CLK_SET_RATE_PARENT;
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case 1:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (hwdata->num == 0 || hwdata->num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case 2:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case 3:
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	}
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INPUT_MASK, val);
> +
> +	return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +		rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* powerdown clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	struct property *prop;
> +	const __be32 *p;
> +	unsigned int num, val;
> +
> +	if (np == NULL)
> +		return;
> +
> +	/*
> +	 * property pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(client->dev.of_node, "pll-source",
> +				 prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			break;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p)
> +			break;
> +
> +		if (_si5351_pll_reparent(drvdata, num, val))
> +			dev_warn(&client->dev,
> +				 "unable to reparent pll %d to %d\n",
> +				 num, val);
> +	}
> +
> +	for_each_child_of_node(client->dev.of_node, np) {
> +		if (of_property_read_u32(np, "reg", &num)) {
> +			dev_err(&client->dev, "missing reg property of %s\n",
> +				np->full_name);
> +			continue;
> +		}
> +
> +		if (of_property_read_bool(np, "pll-master"))
> +			_si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +		if (!of_property_read_u32(np, "drive-strength", &val)) {
> +			if (_si5351_clkout_set_drive_strength(drvdata,
> +							      num, val))
> +				dev_warn(&client->dev,
> +					 "unable to set drive strength of %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +			if (_si5351_msynth_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent multisynth %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-source", &val)) {
> +			if (_si5351_clkout_reparent(drvdata, num, val))
> +				dev_warn(&client->dev,
> +					 "unable to reparent clockout %d to %d\n",
> +					 num, val);
> +		}
> +
> +		if (!of_property_read_u32(np, "clock-frequency", &val))
> +			clk_set_rate(drvdata->onecell.clks[num], val);
> +	}
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +	struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +	struct device_node *np = client->dev.of_node;
> +	const struct of_device_id *match;
> +
> +	if (np == NULL)
> +		return -EINVAL;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	drvdata->variant = (enum si5351_variant)match->data;
> +	drvdata->pxtal = of_clk_get(np, 0);
> +	drvdata->pclkin = of_clk_get(np, 1);
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +			       GFP_KERNEL);
> +	if (drvdata == NULL) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	ret = si5351_dt_parse(client, drvdata);
> +	if (ret)
> +		return ret;
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		init.parent_names = &drvdata->pxtal->name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			init.parent_names = &drvdata->pclkin->name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(struct clk_init_data));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->clkout = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(struct clk_init_data));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	/* setup clock setup from DT */
> +	si5351_dt_setup(client, drvdata);
> +
> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +			    &drvdata->onecell);
> +
> +	dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +	return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +	i2c_set_clientdata(client, NULL);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = si5351_dt_ids,
> +	},
> +	.probe = si5351_i2c_probe,
> +	.remove = si5351_i2c_remove,
> +	.id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +	return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +	i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_MASK			(3<<0)
> +#define  SI5351_CLK_DRIVE_2MA			(0<<0)
> +#define  SI5351_CLK_DRIVE_4MA			(1<<0)
> +#define  SI5351_CLK_DRIVE_6MA			(2<<0)
> +#define  SI5351_CLK_DRIVE_8MA			(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] clk: add si5351 i2c common clock driver
  2013-03-18 10:43   ` Sebastian Hesselbarth
  (?)
@ 2013-03-20  0:26     ` Mike Turquette
  -1 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-03-20  0:26 UTC (permalink / raw)
  To: Sebastian Hesselbarth, Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	Daniel Mack, Jean-Francois Moine, devicetree-discuss, linux-doc,
	linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-03-18 03:43:17)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 

I've just pushed out a new clk-next branch which includes Ulf Hansson's
series to introduce the .is_prepared callback.  Perhaps you can use that
where you were previously using .is_enabled?

Regards,
Mike

> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1709 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..f73d2d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +       compatible = "fixed-clock";
> +       #clock-cells = <0>;
> +       clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +       /* Si5351a msop10 i2c clock generator */
> +       si5351a: clock-generator@60 {
> +               compatible = "silabs,si5351a-msop";
> +               reg = <0x60>;
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +               #clock-cells = <1>;
> +
> +               /* connect xtal input to 25MHz reference */
> +               clocks = <&ref25>;
> +
> +               /* connect xtal input as source of pll0 and pll1 */
> +               pll-source = <0 0>, <1 0>;
> +
> +               /*
> +                * overwrite clkout0 configuration with:
> +                * - 8mA output drive strength
> +                * - pll0 as clock source of multisynth0
> +                * - multisynth0 as clock source of output divider
> +                * - multisynth0 can change pll0
> +                * - set initial clock frequency of 74.25MHz
> +                */
> +               clkout0 {
> +                       reg = <0>;
> +                       drive-strength = <8>;
> +                       multisynth-source = <0>;
> +                       clock-source = <0>;
> +                       pll-master;
> +                       clock-frequency = <74250000>;
> +               };
> +
> +               /*
> +                * overwrite clkout1 configuration with:
> +                * - 4mA output drive strength
> +                * - pll1 as clock source of multisynth1
> +                * - multisynth1 as clock source of output divider
> +                * - multisynth1 can change pll1
> +                */
> +               clkout1 {
> +                       reg = <1>;
> +                       drive-strength = <4>;
> +                       multisynth-source = <1>;
> +                       clock-source = <0>;
> +                       pll-master;
> +               };
> +
> +               /*
> +                * overwrite clkout2 configuration with:
> +                * - xtal as clock source of output divider
> +                */
> +               clkout2 {
> +                       reg = <2>;
> +                       clock-source = <2>;
> +               };
> +       };
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung       Samsung Semiconductor
>  sbs    Smart Battery System
>  schindler      Schindler
>  sil    Silicon Image
> +silabs Silicon Laboratories
>  simtek
>  sirf   SiRF Technology, Inc.
>  snps   Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index e23e471..aba4f59 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
>         ---help---
>           This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +       tristate "Clock driver for SiLabs 5351A/B/C"
> +       depends on I2C
> +       select REGMAP_I2C
> +       select RATIONAL
> +       ---help---
> +         This driver supports Silicon Labs 5351A/B/C programmable clock
> +         generators.
> +
>  config CLK_TWL6040
>         tristate "External McPDM functional clock from twl6040"
>         depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 095899e..8f58889 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_X86)             += x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)      += clk-twl6040.o
>  
>  # Debug drivers
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..38540e7
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1429 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>
> +#include <linux/clkdev.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +enum si5351_variant {
> +       SI5351_VARIANT_A = 1,
> +       SI5351_VARIANT_A3 = 2,
> +       SI5351_VARIANT_B = 3,
> +       SI5351_VARIANT_C = 4,
> +};
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +       unsigned long   p1;
> +       unsigned long   p2;
> +       unsigned long   p3;
> +       int             valid;
> +};
> +
> +struct si5351_hw_data {
> +       struct clk_hw                   hw;
> +       struct si5351_driver_data       *drvdata;
> +       struct si5351_parameters        params;
> +       unsigned char                   num;
> +};
> +
> +struct si5351_driver_data {
> +       enum si5351_variant     variant;
> +       struct i2c_client       *client;
> +       struct regmap           *regmap;
> +       struct clk_onecell_data onecell;
> +
> +       struct clk              *pxtal;
> +       struct clk_hw           xtal;
> +       struct clk              *pclkin;
> +       struct clk_hw           clkin;
> +
> +       struct si5351_hw_data   pll[2];
> +       struct si5351_hw_data   *msynth;
> +       struct si5351_hw_data   *clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +       "xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +       "plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +       "ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +       "clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
> +       unsigned char reg)
> +{
> +       unsigned int val;
> +       int ret;
> +
> +       ret = regmap_read(drvdata->regmap, reg, &val);
> +       if (ret) {
> +               dev_err(&drvdata->client->dev,
> +                       "unable to read from reg%02x\n", reg);
> +               return 0;
> +       }
> +
> +       return (unsigned char)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char count, unsigned char *buf)
> +{
> +       return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char val)
> +{
> +       return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char count, const unsigned char *buf)
> +{
> +       return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char mask, unsigned char val)
> +{
> +       return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline unsigned char si5351_msynth_params_address(int num)
> +{
> +       if (num > 5)
> +               return SI5351_CLK6_PARAMETERS + (num - 6);
> +       return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +       unsigned char reg, struct si5351_parameters *params)
> +{
> +       unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = si5351_reg_read(drvdata, reg);
> +               params->p1 = buf[0];
> +               params->p2 = 0;
> +               params->p3 = 1;
> +               break;
> +       default:
> +               si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +               params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +               params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +               params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +       }
> +       params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +       unsigned char reg, struct si5351_parameters *params)
> +{
> +       unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = params->p1 & 0xff;
> +               si5351_reg_write(drvdata, reg, buf[0]);
> +               break;
> +       default:
> +               buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +               buf[1] = params->p3 & 0xff;
> +               /* save rdiv and divby4 */
> +               buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +               buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +               buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +               buf[4] = params->p1 & 0xff;
> +               buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +                       ((params->p2 & 0xf0000) >> 16);
> +               buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +               buf[7] = params->p2 & 0xff;
> +               si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +       }
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +       switch (reg) {
> +       case SI5351_DEVICE_STATUS:
> +       case SI5351_INTERRUPT_STATUS:
> +       case SI5351_PLL_RESET:
> +               return true;
> +       }
> +       return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +       /* reserved registers */
> +       if (reg >= 4 && reg <= 8)
> +               return false;
> +       if (reg >= 10 && reg <= 14)
> +               return false;
> +       if (reg >= 173 && reg <= 176)
> +               return false;
> +       if (reg >= 178 && reg <= 182)
> +               return false;
> +       /* read-only */
> +       if (reg == SI5351_DEVICE_STATUS)
> +               return false;
> +       return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +       .reg_bits = 8,
> +       .val_bits = 8,
> +       .cache_type = REGCACHE_RBTREE,
> +       .max_register = 187,
> +       .writeable_reg = si5351_regmap_is_writeable,
> +       .volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +       .prepare = si5351_xtal_prepare,
> +       .unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +                                             unsigned long parent_rate)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       unsigned long rate;
> +       unsigned char idiv;
> +
> +       rate = parent_rate;
> +       if (parent_rate > 160000000) {
> +               idiv = SI5351_CLKIN_DIV_8;
> +               rate /= 8;
> +       } else if (parent_rate > 80000000) {
> +               idiv = SI5351_CLKIN_DIV_4;
> +               rate /= 4;
> +       } else if (parent_rate > 40000000) {
> +               idiv = SI5351_CLKIN_DIV_2;
> +               rate /= 2;
> +       } else {
> +               idiv = SI5351_CLKIN_DIV_1;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       SI5351_CLKIN_DIV_MASK, idiv);
> +
> +       dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +               __func__, (1 << (idiv >> 6)), rate);
> +
> +       return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +       .prepare = si5351_clkin_prepare,
> +       .unprepare = si5351_clkin_unprepare,
> +       .recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +       return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +                                            unsigned long parent_rate)
> +{
> +       return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long parent)
> +{
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +       .prepare = si5351_vxco_prepare,
> +       .unprepare = si5351_vxco_unprepare,
> +       .recalc_rate = si5351_vxco_recalc_rate,
> +       .set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +                                      unsigned char num, unsigned char parent)
> +{
> +       if (num > 2 ||
> +           (drvdata->variant == SI5351_VARIANT_B && num > 1))
> +               return -EINVAL;
> +
> +       if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
> +               return -EINVAL;
> +
> +       return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
> +                             drvdata->clkin.clk : drvdata->xtal.clk);
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char mask = (hwdata->num == 0) ?
> +               SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +       return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char mask = (hwdata->num == 0) ?
> +               SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +       if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +           index > 0)
> +               return -EPERM;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       mask, (index) ? 0 : mask);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = (hwdata->num == 0) ?
> +               SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +       unsigned long long rate;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +       rate  = hwdata->params.p1 * hwdata->params.p3;
> +       rate += 512 * hwdata->params.p3;
> +       rate += hwdata->params.p2;
> +       rate *= parent_rate;
> +       do_div(rate, 128 * hwdata->params.p3);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long rfrac, denom, a, b, c;
> +       unsigned long long lltmp;
> +
> +       if (rate < SI5351_PLL_VCO_MIN)
> +               rate = SI5351_PLL_VCO_MIN;
> +       if (rate > SI5351_PLL_VCO_MAX)
> +               rate = SI5351_PLL_VCO_MAX;
> +
> +       /* determine integer part of feedback equation */
> +       a = rate / *parent_rate;
> +
> +       if (a < SI5351_PLL_A_MIN)
> +               rate = *parent_rate * SI5351_PLL_A_MIN;
> +       if (a > SI5351_PLL_A_MAX)
> +               rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +       /* find best approximation for b/c = fVCO mod fIN */
> +       denom = 1000 * 1000;
> +       lltmp = rate % (*parent_rate);
> +       lltmp *= denom;
> +       do_div(lltmp, *parent_rate);
> +       rfrac = (unsigned long)lltmp;
> +
> +       b = 0;
> +       c = 1;
> +       if (rfrac)
> +               rational_best_approximation(rfrac, denom,
> +                                   SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +       /* calculate parameters */
> +       hwdata->params.p3  = c;
> +       hwdata->params.p2  = (128 * b) % c;
> +       hwdata->params.p1  = 128 * a;
> +       hwdata->params.p1 += (128 * b / c);
> +       hwdata->params.p1 -= 512;
> +
> +       /* recalculate rate by fIN * (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= b;
> +       do_div(lltmp, c);
> +
> +       rate  = (unsigned long)lltmp;
> +       rate += *parent_rate * a;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = (hwdata->num == 0) ?
> +               SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +       unsigned char val;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       /* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +               SI5351_CLK_INTEGER_MODE,
> +               (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +       /* reset pll */
> +       val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
> +       si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +       .set_parent = si5351_pll_set_parent,
> +       .get_parent = si5351_pll_get_parent,
> +       .recalc_rate = si5351_pll_recalc_rate,
> +       .round_rate = si5351_pll_round_rate,
> +       .set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static inline void _si5351_msynth_set_pll_master(
> +       struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> +{
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return;
> +
> +       if (is_master)
> +               drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +       else
> +               drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +}
> +
> +static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +                                 unsigned char num, unsigned char parent)
> +{
> +       if (parent > 2)
> +               return -EINVAL;
> +
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       return clk_set_parent(drvdata->msynth[num].hw.clk,
> +                             drvdata->pll[parent].hw.clk);
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +       return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_PLL_SELECT,
> +                       (index) ? SI5351_CLK_PLL_SELECT : 0);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +       unsigned long long rate;
> +       unsigned long m;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /*
> +        * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +        * multisync6-7: fOUT = fIN / P1
> +        */
> +       rate = parent_rate;
> +       if (hwdata->num > 5) {
> +               m = hwdata->params.p1;
> +       } else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +                   SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +               m = 4;
> +       } else {
> +               rate *= 128 * hwdata->params.p3;
> +               m = hwdata->params.p1 * hwdata->params.p3;
> +               m += hwdata->params.p2;
> +               m += 512 * hwdata->params.p3;
> +       }
> +
> +       if (m == 0)
> +               return 0;
> +       do_div(rate, m);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               m, parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long long lltmp;
> +       unsigned long a, b, c;
> +       int divby4;
> +
> +       /* multisync6-7 can only handle freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +       /* multisync frequency is 1MHz .. 160MHz */
> +       if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH_MAX_FREQ;
> +       if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +               rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +       divby4 = 0;
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* multisync can set pll */
> +       if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +               /*
> +                * find largest integer divider for max
> +                * vco frequency and given target rate
> +                */
> +               if (divby4 == 0) {
> +                       lltmp = SI5351_PLL_VCO_MAX;
> +                       do_div(lltmp, rate);
> +                       a = (unsigned long)lltmp;
> +               } else
> +                       a = 4;
> +
> +               b = 0;
> +               c = 1;
> +
> +               *parent_rate = a * rate;
> +       } else {
> +               unsigned long rfrac, denom;
> +
> +               /* disable divby4 */
> +               if (divby4) {
> +                       rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +                       divby4 = 0;
> +               }
> +
> +               /* determine integer part of divider equation */
> +               a = *parent_rate / rate;
> +               if (a < SI5351_MULTISYNTH_A_MIN)
> +                       a = SI5351_MULTISYNTH_A_MIN;
> +               if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +                       a = SI5351_MULTISYNTH67_A_MAX;
> +               else if (a > SI5351_MULTISYNTH_A_MAX)
> +                       a = SI5351_MULTISYNTH_A_MAX;
> +
> +               /* find best approximation for b/c = fVCO mod fOUT */
> +               denom = 1000 * 1000;
> +               lltmp = (*parent_rate) % rate;
> +               lltmp *= denom;
> +               do_div(lltmp, rate);
> +               rfrac = (unsigned long)lltmp;
> +
> +               b = 0;
> +               c = 1;
> +               if (rfrac)
> +                       rational_best_approximation(rfrac, denom,
> +                           SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +                           &b, &c);
> +       }
> +
> +       /* recalculate rate by fOUT = fIN / (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= c;
> +       do_div(lltmp, a * c + b);
> +       rate  = (unsigned long)lltmp;
> +
> +       /* calculate parameters */
> +       if (divby4) {
> +               hwdata->params.p3 = 1;
> +               hwdata->params.p2 = 0;
> +               hwdata->params.p1 = 0;
> +       } else {
> +               hwdata->params.p3  = c;
> +               hwdata->params.p2  = (128 * b) % c;
> +               hwdata->params.p1  = 128 * a;
> +               hwdata->params.p1 += (128 * b / c);
> +               hwdata->params.p1 -= 512;
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
> +               rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +       int divby4 = 0;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* enable/disable integer mode and divby4 on multisynth0-5 */
> +       if (hwdata->num < 6) {
> +               si5351_set_bits(hwdata->drvdata, reg + 2,
> +                               SI5351_OUTPUT_CLK_DIVBY4,
> +                               (divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INTEGER_MODE,
> +                       (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               divby4, parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +       .set_parent = si5351_msynth_set_parent,
> +       .get_parent = si5351_msynth_get_parent,
> +       .recalc_rate = si5351_msynth_recalc_rate,
> +       .round_rate = si5351_msynth_round_rate,
> +       .set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
> +                                    unsigned char num, unsigned char drive)
> +{
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       switch (drive) {
> +       case 2:
> +               drive = SI5351_CLK_DRIVE_2MA;
> +               break;
> +       case 4:
> +               drive = SI5351_CLK_DRIVE_4MA;
> +               break;
> +       case 6:
> +               drive = SI5351_CLK_DRIVE_6MA;
> +               break;
> +       case 8:
> +               drive = SI5351_CLK_DRIVE_8MA;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_DRIVE_MASK, drive);
> +
> +       return 0;
> +}
> +
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +                                 unsigned char num, unsigned char parent)
> +{
> +       struct clk *pclk;
> +
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +       switch (parent) {
> +       case 0:
> +               pclk = drvdata->msynth[num].hw.clk;
> +               drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +               break;
> +       case 1:
> +               pclk = drvdata->msynth[0].hw.clk;
> +               if (num >= 4)
> +                       pclk = drvdata->msynth[4].hw.clk;
> +               break;
> +       case 2:
> +               pclk = drvdata->xtal.clk;
> +               break;
> +       case 3:
> +               if (drvdata->variant != SI5351_VARIANT_C)
> +                       return -EINVAL;
> +               pclk = drvdata->clkin.clk;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +       return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), 0);
> +       return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       int index = 0;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +       switch (val & SI5351_CLK_INPUT_MASK) {
> +       case SI5351_CLK_INPUT_MULTISYNTH_N:
> +               index = 0;
> +               break;
> +       case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +               index = 1;
> +               break;
> +       case SI5351_CLK_INPUT_XTAL:
> +               index = 2;
> +               break;
> +       case SI5351_CLK_INPUT_CLKIN:
> +               index = 3;
> +               break;
> +       }
> +
> +       return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned val;
> +
> +       val = 0;
> +       hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +       switch (index) {
> +       case 0:
> +               hw->clk->flags |= CLK_SET_RATE_PARENT;
> +               val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               break;
> +       case 1:
> +               /* clk0/clk4 can only connect to its own multisync */
> +               if (hwdata->num == 0 || hwdata->num == 4)
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               else
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +               break;
> +       case 2:
> +               val = SI5351_CLK_INPUT_XTAL;
> +               break;
> +       case 3:
> +               val = SI5351_CLK_INPUT_CLKIN;
> +               break;
> +       }
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INPUT_MASK, val);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg;
> +       unsigned char rdiv;
> +
> +       if (hwdata->num > 5)
> +               reg = si5351_msynth_params_address(hwdata->num) + 2;
> +       else
> +               reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +       rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +       if (hwdata->num == 6) {
> +               rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +       } else {
> +               rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +               rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +       }
> +
> +       return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char rdiv;
> +
> +       /* clkout6/7 can only handle output freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +               rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +       /* clkout freqency is 8kHz - 160MHz */
> +       if (rate > SI5351_CLKOUT_MAX_FREQ)
> +               rate = SI5351_CLKOUT_MAX_FREQ;
> +       if (rate < SI5351_CLKOUT_MIN_FREQ)
> +               rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +       /* request frequency if multisync master */
> +       if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +               /* use r divider for frequencies below 1MHz */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +                      rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +                       rdiv += 1;
> +                       rate *= 2;
> +               }
> +               *parent_rate = rate;
> +       } else {
> +               unsigned long new_rate, new_err, err;
> +
> +               /* round to closed rdiv */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               new_rate = *parent_rate;
> +               err = abs(new_rate - rate);
> +               do {
> +                       new_rate >>= 1;
> +                       new_err = abs(new_rate - rate);
> +                       if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                               break;
> +                       rdiv++;
> +                       err = new_err;
> +               } while (1);
> +       }
> +       rate = *parent_rate >> rdiv;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +               rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long new_rate, new_err, err;
> +       unsigned char rdiv;
> +
> +       /* round to closed rdiv */
> +       rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +       new_rate = parent_rate;
> +       err = abs(new_rate - rate);
> +       do {
> +               new_rate >>= 1;
> +               new_err = abs(new_rate - rate);
> +               if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                       break;
> +               rdiv++;
> +               err = new_err;
> +       } while (1);
> +
> +       /* powerdown clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +       /* write output divider */
> +       switch (hwdata->num) {
> +       case 6:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +               break;
> +       case 7:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +               break;
> +       default:
> +               si5351_set_bits(hwdata->drvdata,
> +                               si5351_msynth_params_address(hwdata->num) + 2,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +       }
> +
> +       /* powerup clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +       .prepare = si5351_clkout_prepare,
> +       .unprepare = si5351_clkout_unprepare,
> +       .set_parent = si5351_clkout_set_parent,
> +       .get_parent = si5351_clkout_get_parent,
> +       .recalc_rate = si5351_clkout_recalc_rate,
> +       .round_rate = si5351_clkout_round_rate,
> +       .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +       struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +       struct device_node *np = client->dev.of_node;
> +       struct property *prop;
> +       const __be32 *p;
> +       unsigned int num, val;
> +
> +       if (np == NULL)
> +               return;
> +
> +       /*
> +        * property pll-source : <num src>, [<..>]
> +        * allow to selectively set pll source
> +        */
> +       of_property_for_each_u32(client->dev.of_node, "pll-source",
> +                                prop, p, num) {
> +               if (num >= 2) {
> +                       dev_err(&client->dev,
> +                               "invalid pll %d on pll-source prop\n", num);
> +                       break;
> +               }
> +
> +               p = of_prop_next_u32(prop, p, &val);
> +               if (!p)
> +                       break;
> +
> +               if (_si5351_pll_reparent(drvdata, num, val))
> +                       dev_warn(&client->dev,
> +                                "unable to reparent pll %d to %d\n",
> +                                num, val);
> +       }
> +
> +       for_each_child_of_node(client->dev.of_node, np) {
> +               if (of_property_read_u32(np, "reg", &num)) {
> +                       dev_err(&client->dev, "missing reg property of %s\n",
> +                               np->full_name);
> +                       continue;
> +               }
> +
> +               if (of_property_read_bool(np, "pll-master"))
> +                       _si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +               if (!of_property_read_u32(np, "drive-strength", &val)) {
> +                       if (_si5351_clkout_set_drive_strength(drvdata,
> +                                                             num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to set drive strength of %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +                       if (_si5351_msynth_reparent(drvdata, num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to reparent multisynth %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "clock-source", &val)) {
> +                       if (_si5351_clkout_reparent(drvdata, num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to reparent clockout %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "clock-frequency", &val))
> +                       clk_set_rate(drvdata->onecell.clks[num], val);
> +       }
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +       { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +       { .compatible = "silabs,si5351a-msop",
> +                                        .data = (void *)SI5351_VARIANT_A3, },
> +       { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +       { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +       struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +       struct device_node *np = client->dev.of_node;
> +       const struct of_device_id *match;
> +
> +       if (np == NULL)
> +               return -EINVAL;
> +
> +       match = of_match_node(si5351_dt_ids, np);
> +       if (match == NULL)
> +               return -EINVAL;
> +
> +       drvdata->variant = (enum si5351_variant)match->data;
> +       drvdata->pxtal = of_clk_get(np, 0);
> +       drvdata->pclkin = of_clk_get(np, 1);
> +
> +       return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +       struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +       struct si5351_driver_data *drvdata;
> +       struct clk_init_data init;
> +       struct clk *clk;
> +       const char *parent_names[4];
> +       u8 num_parents, num_clocks;
> +       int ret, n;
> +
> +       drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +                              GFP_KERNEL);
> +       if (drvdata == NULL) {
> +               dev_err(&client->dev, "unable to allocate driver data\n");
> +               return -ENOMEM;
> +       }
> +
> +       ret = si5351_dt_parse(client, drvdata);
> +       if (ret)
> +               return ret;
> +
> +       i2c_set_clientdata(client, drvdata);
> +       drvdata->client = client;
> +       drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +       if (IS_ERR(drvdata->regmap)) {
> +               dev_err(&client->dev, "failed to allocate register map\n");
> +               return PTR_ERR(drvdata->regmap);
> +       }
> +
> +       /* Disable interrupts */
> +       si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +       /* Set disabled output drivers to drive low */
> +       si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +       si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +       /* Ensure pll select is on XTAL for Si5351A/B */
> +       if (drvdata->variant != SI5351_VARIANT_C)
> +               si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                               SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +       /* register xtal input clock gate */
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       init.name = si5351_input_names[0];
> +       init.ops = &si5351_xtal_ops;
> +       init.flags = 0;
> +       if (!IS_ERR(drvdata->pxtal)) {
> +               init.parent_names = &drvdata->pxtal->name;
> +               init.num_parents = 1;
> +       }
> +       drvdata->xtal.init = &init;
> +       clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return PTR_ERR(clk);
> +       }
> +
> +       /* register clkin input clock gate */
> +       if (drvdata->variant == SI5351_VARIANT_C) {
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_input_names[1];
> +               init.ops = &si5351_clkin_ops;
> +               if (!IS_ERR(drvdata->pclkin)) {
> +                       init.parent_names = &drvdata->pclkin->name;
> +                       init.num_parents = 1;
> +               }
> +               drvdata->clkin.init = &init;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return PTR_ERR(clk);
> +               }
> +       }
> +
> +       /* Si5351C allows to mux either xtal or clkin to PLL input */
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +       parent_names[0] = si5351_input_names[0];
> +       parent_names[1] = si5351_input_names[1];
> +
> +       /* register PLLA */
> +       drvdata->pll[0].num = 0;
> +       drvdata->pll[0].drvdata = drvdata;
> +       drvdata->pll[0].hw.init = &init;
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       init.name = si5351_pll_names[0];
> +       init.ops = &si5351_pll_ops;
> +       init.flags = 0;
> +       init.parent_names = parent_names;
> +       init.num_parents = num_parents;
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register PLLB or VXCO (Si5351B) */
> +       drvdata->pll[1].num = 1;
> +       drvdata->pll[1].drvdata = drvdata;
> +       drvdata->pll[1].hw.init = &init;
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       if (drvdata->variant == SI5351_VARIANT_B) {
> +               init.name = si5351_pll_names[2];
> +               init.ops = &si5351_vxco_ops;
> +               init.flags = CLK_IS_ROOT;
> +               init.parent_names = NULL;
> +               init.num_parents = 0;
> +       } else {
> +               init.name = si5351_pll_names[1];
> +               init.ops = &si5351_pll_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +       }
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register clk multisync and clk out divider */
> +       num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +       parent_names[0] = si5351_pll_names[0];
> +       if (drvdata->variant == SI5351_VARIANT_B)
> +               parent_names[1] = si5351_pll_names[2];
> +       else
> +               parent_names[1] = si5351_pll_names[1];
> +
> +       drvdata->msynth = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +       drvdata->clkout = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +       drvdata->onecell.clk_num = num_clocks;
> +       drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +       if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +                   !drvdata->onecell.clks))
> +               return -ENOMEM;
> +
> +       for (n = 0; n < num_clocks; n++) {
> +               drvdata->msynth[n].num = n;
> +               drvdata->msynth[n].drvdata = drvdata;
> +               drvdata->msynth[n].hw.init = &init;
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_msynth_names[n];
> +               init.ops = &si5351_msynth_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = 2;
> +               clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +       parent_names[2] = si5351_input_names[0];
> +       parent_names[3] = si5351_input_names[1];
> +       for (n = 0; n < num_clocks; n++) {
> +               parent_names[0] = si5351_msynth_names[n];
> +               parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +                       si5351_msynth_names[4];
> +
> +               drvdata->clkout[n].num = n;
> +               drvdata->clkout[n].drvdata = drvdata;
> +               drvdata->clkout[n].hw.init = &init;
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_clkout_names[n];
> +               init.ops = &si5351_clkout_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +               drvdata->onecell.clks[n] = clk;
> +       }
> +
> +       /* setup clock setup from DT */
> +       si5351_dt_setup(client, drvdata);
> +
> +       of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +                           &drvdata->onecell);
> +
> +       dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +       return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +       i2c_set_clientdata(client, NULL);
> +       return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +       { "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +       { "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +       .driver = {
> +               .name = "si5351",
> +               .of_match_table = si5351_dt_ids,
> +       },
> +       .probe = si5351_i2c_probe,
> +       .remove = si5351_i2c_remove,
> +       .id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +       return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +       i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR                   0x60
> +
> +#define SI5351_PLL_VCO_MIN                     600000000
> +#define SI5351_PLL_VCO_MAX                     900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ             1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ          150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ             160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ           SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ                 8000
> +#define SI5351_CLKOUT_MAX_FREQ                 SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ               SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN                       15
> +#define SI5351_PLL_A_MAX                       90
> +#define SI5351_PLL_B_MAX                       (SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX                       1048575
> +#define SI5351_MULTISYNTH_A_MIN                        6
> +#define SI5351_MULTISYNTH_A_MAX                        1800
> +#define SI5351_MULTISYNTH67_A_MAX              254
> +#define SI5351_MULTISYNTH_B_MAX                        (SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX                        1048575
> +#define SI5351_MULTISYNTH_P1_MAX               ((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX               ((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX               ((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS                   0
> +#define SI5351_INTERRUPT_STATUS                        1
> +#define SI5351_INTERRUPT_MASK                  2
> +#define  SI5351_STATUS_SYS_INIT                        (1<<7)
> +#define  SI5351_STATUS_LOL_B                   (1<<6)
> +#define  SI5351_STATUS_LOL_A                   (1<<5)
> +#define  SI5351_STATUS_LOS                     (1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL              3
> +#define SI5351_OEB_PIN_ENABLE_CTRL             9
> +#define SI5351_PLL_INPUT_SOURCE                        15
> +#define  SI5351_CLKIN_DIV_MASK                 (3<<6)
> +#define  SI5351_CLKIN_DIV_1                    (0<<6)
> +#define  SI5351_CLKIN_DIV_2                    (1<<6)
> +#define  SI5351_CLKIN_DIV_4                    (2<<6)
> +#define  SI5351_CLKIN_DIV_8                    (3<<6)
> +#define  SI5351_PLLB_SOURCE                    (1<<3)
> +#define  SI5351_PLLA_SOURCE                    (1<<2)
> +
> +#define SI5351_CLK0_CTRL                       16
> +#define SI5351_CLK1_CTRL                       17
> +#define SI5351_CLK2_CTRL                       18
> +#define SI5351_CLK3_CTRL                       19
> +#define SI5351_CLK4_CTRL                       20
> +#define SI5351_CLK5_CTRL                       21
> +#define SI5351_CLK6_CTRL                       22
> +#define SI5351_CLK7_CTRL                       23
> +#define  SI5351_CLK_POWERDOWN                  (1<<7)
> +#define  SI5351_CLK_INTEGER_MODE               (1<<6)
> +#define  SI5351_CLK_PLL_SELECT                 (1<<5)
> +#define  SI5351_CLK_INVERT                     (1<<4)
> +#define  SI5351_CLK_INPUT_MASK                 (3<<2)
> +#define  SI5351_CLK_INPUT_XTAL                 (0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN                        (1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4       (2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N         (3<<2)
> +#define  SI5351_CLK_DRIVE_MASK                 (3<<0)
> +#define  SI5351_CLK_DRIVE_2MA                  (0<<0)
> +#define  SI5351_CLK_DRIVE_4MA                  (1<<0)
> +#define  SI5351_CLK_DRIVE_6MA                  (2<<0)
> +#define  SI5351_CLK_DRIVE_8MA                  (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE            24
> +#define SI5351_CLK7_4_DISABLE_STATE            25
> +#define  SI5351_CLK_DISABLE_STATE_LOW          0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH         1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT                2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER                3
> +
> +#define SI5351_PARAMETERS_LENGTH               8
> +#define SI5351_PLLA_PARAMETERS                 26
> +#define SI5351_PLLB_PARAMETERS                 34
> +#define SI5351_CLK0_PARAMETERS                 42
> +#define SI5351_CLK1_PARAMETERS                 50
> +#define SI5351_CLK2_PARAMETERS                 58
> +#define SI5351_CLK3_PARAMETERS                 66
> +#define SI5351_CLK4_PARAMETERS                 74
> +#define SI5351_CLK5_PARAMETERS                 82
> +#define SI5351_CLK6_PARAMETERS                 90
> +#define SI5351_CLK7_PARAMETERS                 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER           92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK            (7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK           (7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT           4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT          0
> +#define  SI5351_OUTPUT_CLK_DIV_1               0
> +#define  SI5351_OUTPUT_CLK_DIV_2               1
> +#define  SI5351_OUTPUT_CLK_DIV_4               2
> +#define  SI5351_OUTPUT_CLK_DIV_8               3
> +#define  SI5351_OUTPUT_CLK_DIV_16              4
> +#define  SI5351_OUTPUT_CLK_DIV_32              5
> +#define  SI5351_OUTPUT_CLK_DIV_64              6
> +#define  SI5351_OUTPUT_CLK_DIV_128             7
> +#define  SI5351_OUTPUT_CLK_DIVBY4              (3<<2)
> +
> +#define SI5351_SSC_PARAM0                      149
> +#define SI5351_SSC_PARAM1                      150
> +#define SI5351_SSC_PARAM2                      151
> +#define SI5351_SSC_PARAM3                      152
> +#define SI5351_SSC_PARAM4                      153
> +#define SI5351_SSC_PARAM5                      154
> +#define SI5351_SSC_PARAM6                      155
> +#define SI5351_SSC_PARAM7                      156
> +#define SI5351_SSC_PARAM8                      157
> +#define SI5351_SSC_PARAM9                      158
> +#define SI5351_SSC_PARAM10                     159
> +#define SI5351_SSC_PARAM11                     160
> +#define SI5351_SSC_PARAM12                     161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW             162
> +#define SI5351_VXCO_PARAMETERS_MID             163
> +#define SI5351_VXCO_PARAMETERS_HIGH            164
> +
> +#define SI5351_CLK0_PHASE_OFFSET               165
> +#define SI5351_CLK1_PHASE_OFFSET               166
> +#define SI5351_CLK2_PHASE_OFFSET               167
> +#define SI5351_CLK3_PHASE_OFFSET               168
> +#define SI5351_CLK4_PHASE_OFFSET               169
> +#define SI5351_CLK5_PHASE_OFFSET               170
> +
> +#define SI5351_PLL_RESET                       177
> +#define  SI5351_PLL_RESET_B                    (1<<7)
> +#define  SI5351_PLL_RESET_A                    (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD                    183
> +#define  SI5351_CRYSTAL_LOAD_MASK              (3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF               (1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF               (2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF              (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE                   187
> +#define  SI5351_CLKIN_ENABLE                   (1<<7)
> +#define  SI5351_XTAL_ENABLE                    (1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE              (1<<4)
> +
> +#endif
> -- 
> 1.7.10.4

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-20  0:26     ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-03-20  0:26 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	Daniel Mack, Jean-Francois Moine, devicetree-discuss, linux-doc,
	linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-03-18 03:43:17)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 

I've just pushed out a new clk-next branch which includes Ulf Hansson's
series to introduce the .is_prepared callback.  Perhaps you can use that
where you were previously using .is_enabled?

Regards,
Mike

> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1709 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..f73d2d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +       compatible = "fixed-clock";
> +       #clock-cells = <0>;
> +       clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +       /* Si5351a msop10 i2c clock generator */
> +       si5351a: clock-generator@60 {
> +               compatible = "silabs,si5351a-msop";
> +               reg = <0x60>;
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +               #clock-cells = <1>;
> +
> +               /* connect xtal input to 25MHz reference */
> +               clocks = <&ref25>;
> +
> +               /* connect xtal input as source of pll0 and pll1 */
> +               pll-source = <0 0>, <1 0>;
> +
> +               /*
> +                * overwrite clkout0 configuration with:
> +                * - 8mA output drive strength
> +                * - pll0 as clock source of multisynth0
> +                * - multisynth0 as clock source of output divider
> +                * - multisynth0 can change pll0
> +                * - set initial clock frequency of 74.25MHz
> +                */
> +               clkout0 {
> +                       reg = <0>;
> +                       drive-strength = <8>;
> +                       multisynth-source = <0>;
> +                       clock-source = <0>;
> +                       pll-master;
> +                       clock-frequency = <74250000>;
> +               };
> +
> +               /*
> +                * overwrite clkout1 configuration with:
> +                * - 4mA output drive strength
> +                * - pll1 as clock source of multisynth1
> +                * - multisynth1 as clock source of output divider
> +                * - multisynth1 can change pll1
> +                */
> +               clkout1 {
> +                       reg = <1>;
> +                       drive-strength = <4>;
> +                       multisynth-source = <1>;
> +                       clock-source = <0>;
> +                       pll-master;
> +               };
> +
> +               /*
> +                * overwrite clkout2 configuration with:
> +                * - xtal as clock source of output divider
> +                */
> +               clkout2 {
> +                       reg = <2>;
> +                       clock-source = <2>;
> +               };
> +       };
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung       Samsung Semiconductor
>  sbs    Smart Battery System
>  schindler      Schindler
>  sil    Silicon Image
> +silabs Silicon Laboratories
>  simtek
>  sirf   SiRF Technology, Inc.
>  snps   Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index e23e471..aba4f59 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
>         ---help---
>           This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +       tristate "Clock driver for SiLabs 5351A/B/C"
> +       depends on I2C
> +       select REGMAP_I2C
> +       select RATIONAL
> +       ---help---
> +         This driver supports Silicon Labs 5351A/B/C programmable clock
> +         generators.
> +
>  config CLK_TWL6040
>         tristate "External McPDM functional clock from twl6040"
>         depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 095899e..8f58889 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_X86)             += x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)      += clk-twl6040.o
>  
>  # Debug drivers
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..38540e7
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1429 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>
> +#include <linux/clkdev.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +enum si5351_variant {
> +       SI5351_VARIANT_A = 1,
> +       SI5351_VARIANT_A3 = 2,
> +       SI5351_VARIANT_B = 3,
> +       SI5351_VARIANT_C = 4,
> +};
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +       unsigned long   p1;
> +       unsigned long   p2;
> +       unsigned long   p3;
> +       int             valid;
> +};
> +
> +struct si5351_hw_data {
> +       struct clk_hw                   hw;
> +       struct si5351_driver_data       *drvdata;
> +       struct si5351_parameters        params;
> +       unsigned char                   num;
> +};
> +
> +struct si5351_driver_data {
> +       enum si5351_variant     variant;
> +       struct i2c_client       *client;
> +       struct regmap           *regmap;
> +       struct clk_onecell_data onecell;
> +
> +       struct clk              *pxtal;
> +       struct clk_hw           xtal;
> +       struct clk              *pclkin;
> +       struct clk_hw           clkin;
> +
> +       struct si5351_hw_data   pll[2];
> +       struct si5351_hw_data   *msynth;
> +       struct si5351_hw_data   *clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +       "xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +       "plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +       "ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +       "clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
> +       unsigned char reg)
> +{
> +       unsigned int val;
> +       int ret;
> +
> +       ret = regmap_read(drvdata->regmap, reg, &val);
> +       if (ret) {
> +               dev_err(&drvdata->client->dev,
> +                       "unable to read from reg%02x\n", reg);
> +               return 0;
> +       }
> +
> +       return (unsigned char)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char count, unsigned char *buf)
> +{
> +       return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char val)
> +{
> +       return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char count, const unsigned char *buf)
> +{
> +       return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char mask, unsigned char val)
> +{
> +       return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline unsigned char si5351_msynth_params_address(int num)
> +{
> +       if (num > 5)
> +               return SI5351_CLK6_PARAMETERS + (num - 6);
> +       return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +       unsigned char reg, struct si5351_parameters *params)
> +{
> +       unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = si5351_reg_read(drvdata, reg);
> +               params->p1 = buf[0];
> +               params->p2 = 0;
> +               params->p3 = 1;
> +               break;
> +       default:
> +               si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +               params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +               params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +               params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +       }
> +       params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +       unsigned char reg, struct si5351_parameters *params)
> +{
> +       unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = params->p1 & 0xff;
> +               si5351_reg_write(drvdata, reg, buf[0]);
> +               break;
> +       default:
> +               buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +               buf[1] = params->p3 & 0xff;
> +               /* save rdiv and divby4 */
> +               buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +               buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +               buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +               buf[4] = params->p1 & 0xff;
> +               buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +                       ((params->p2 & 0xf0000) >> 16);
> +               buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +               buf[7] = params->p2 & 0xff;
> +               si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +       }
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +       switch (reg) {
> +       case SI5351_DEVICE_STATUS:
> +       case SI5351_INTERRUPT_STATUS:
> +       case SI5351_PLL_RESET:
> +               return true;
> +       }
> +       return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +       /* reserved registers */
> +       if (reg >= 4 && reg <= 8)
> +               return false;
> +       if (reg >= 10 && reg <= 14)
> +               return false;
> +       if (reg >= 173 && reg <= 176)
> +               return false;
> +       if (reg >= 178 && reg <= 182)
> +               return false;
> +       /* read-only */
> +       if (reg == SI5351_DEVICE_STATUS)
> +               return false;
> +       return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +       .reg_bits = 8,
> +       .val_bits = 8,
> +       .cache_type = REGCACHE_RBTREE,
> +       .max_register = 187,
> +       .writeable_reg = si5351_regmap_is_writeable,
> +       .volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +       .prepare = si5351_xtal_prepare,
> +       .unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +                                             unsigned long parent_rate)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       unsigned long rate;
> +       unsigned char idiv;
> +
> +       rate = parent_rate;
> +       if (parent_rate > 160000000) {
> +               idiv = SI5351_CLKIN_DIV_8;
> +               rate /= 8;
> +       } else if (parent_rate > 80000000) {
> +               idiv = SI5351_CLKIN_DIV_4;
> +               rate /= 4;
> +       } else if (parent_rate > 40000000) {
> +               idiv = SI5351_CLKIN_DIV_2;
> +               rate /= 2;
> +       } else {
> +               idiv = SI5351_CLKIN_DIV_1;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       SI5351_CLKIN_DIV_MASK, idiv);
> +
> +       dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +               __func__, (1 << (idiv >> 6)), rate);
> +
> +       return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +       .prepare = si5351_clkin_prepare,
> +       .unprepare = si5351_clkin_unprepare,
> +       .recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +       return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +                                            unsigned long parent_rate)
> +{
> +       return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long parent)
> +{
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +       .prepare = si5351_vxco_prepare,
> +       .unprepare = si5351_vxco_unprepare,
> +       .recalc_rate = si5351_vxco_recalc_rate,
> +       .set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +                                      unsigned char num, unsigned char parent)
> +{
> +       if (num > 2 ||
> +           (drvdata->variant == SI5351_VARIANT_B && num > 1))
> +               return -EINVAL;
> +
> +       if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
> +               return -EINVAL;
> +
> +       return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
> +                             drvdata->clkin.clk : drvdata->xtal.clk);
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char mask = (hwdata->num == 0) ?
> +               SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +       return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char mask = (hwdata->num == 0) ?
> +               SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +       if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +           index > 0)
> +               return -EPERM;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       mask, (index) ? 0 : mask);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = (hwdata->num == 0) ?
> +               SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +       unsigned long long rate;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +       rate  = hwdata->params.p1 * hwdata->params.p3;
> +       rate += 512 * hwdata->params.p3;
> +       rate += hwdata->params.p2;
> +       rate *= parent_rate;
> +       do_div(rate, 128 * hwdata->params.p3);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long rfrac, denom, a, b, c;
> +       unsigned long long lltmp;
> +
> +       if (rate < SI5351_PLL_VCO_MIN)
> +               rate = SI5351_PLL_VCO_MIN;
> +       if (rate > SI5351_PLL_VCO_MAX)
> +               rate = SI5351_PLL_VCO_MAX;
> +
> +       /* determine integer part of feedback equation */
> +       a = rate / *parent_rate;
> +
> +       if (a < SI5351_PLL_A_MIN)
> +               rate = *parent_rate * SI5351_PLL_A_MIN;
> +       if (a > SI5351_PLL_A_MAX)
> +               rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +       /* find best approximation for b/c = fVCO mod fIN */
> +       denom = 1000 * 1000;
> +       lltmp = rate % (*parent_rate);
> +       lltmp *= denom;
> +       do_div(lltmp, *parent_rate);
> +       rfrac = (unsigned long)lltmp;
> +
> +       b = 0;
> +       c = 1;
> +       if (rfrac)
> +               rational_best_approximation(rfrac, denom,
> +                                   SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +       /* calculate parameters */
> +       hwdata->params.p3  = c;
> +       hwdata->params.p2  = (128 * b) % c;
> +       hwdata->params.p1  = 128 * a;
> +       hwdata->params.p1 += (128 * b / c);
> +       hwdata->params.p1 -= 512;
> +
> +       /* recalculate rate by fIN * (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= b;
> +       do_div(lltmp, c);
> +
> +       rate  = (unsigned long)lltmp;
> +       rate += *parent_rate * a;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = (hwdata->num == 0) ?
> +               SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +       unsigned char val;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       /* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +               SI5351_CLK_INTEGER_MODE,
> +               (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +       /* reset pll */
> +       val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
> +       si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +       .set_parent = si5351_pll_set_parent,
> +       .get_parent = si5351_pll_get_parent,
> +       .recalc_rate = si5351_pll_recalc_rate,
> +       .round_rate = si5351_pll_round_rate,
> +       .set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static inline void _si5351_msynth_set_pll_master(
> +       struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> +{
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return;
> +
> +       if (is_master)
> +               drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +       else
> +               drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +}
> +
> +static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +                                 unsigned char num, unsigned char parent)
> +{
> +       if (parent > 2)
> +               return -EINVAL;
> +
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       return clk_set_parent(drvdata->msynth[num].hw.clk,
> +                             drvdata->pll[parent].hw.clk);
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +       return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_PLL_SELECT,
> +                       (index) ? SI5351_CLK_PLL_SELECT : 0);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +       unsigned long long rate;
> +       unsigned long m;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /*
> +        * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +        * multisync6-7: fOUT = fIN / P1
> +        */
> +       rate = parent_rate;
> +       if (hwdata->num > 5) {
> +               m = hwdata->params.p1;
> +       } else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +                   SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +               m = 4;
> +       } else {
> +               rate *= 128 * hwdata->params.p3;
> +               m = hwdata->params.p1 * hwdata->params.p3;
> +               m += hwdata->params.p2;
> +               m += 512 * hwdata->params.p3;
> +       }
> +
> +       if (m == 0)
> +               return 0;
> +       do_div(rate, m);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               m, parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long long lltmp;
> +       unsigned long a, b, c;
> +       int divby4;
> +
> +       /* multisync6-7 can only handle freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +       /* multisync frequency is 1MHz .. 160MHz */
> +       if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH_MAX_FREQ;
> +       if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +               rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +       divby4 = 0;
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* multisync can set pll */
> +       if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +               /*
> +                * find largest integer divider for max
> +                * vco frequency and given target rate
> +                */
> +               if (divby4 == 0) {
> +                       lltmp = SI5351_PLL_VCO_MAX;
> +                       do_div(lltmp, rate);
> +                       a = (unsigned long)lltmp;
> +               } else
> +                       a = 4;
> +
> +               b = 0;
> +               c = 1;
> +
> +               *parent_rate = a * rate;
> +       } else {
> +               unsigned long rfrac, denom;
> +
> +               /* disable divby4 */
> +               if (divby4) {
> +                       rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +                       divby4 = 0;
> +               }
> +
> +               /* determine integer part of divider equation */
> +               a = *parent_rate / rate;
> +               if (a < SI5351_MULTISYNTH_A_MIN)
> +                       a = SI5351_MULTISYNTH_A_MIN;
> +               if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +                       a = SI5351_MULTISYNTH67_A_MAX;
> +               else if (a > SI5351_MULTISYNTH_A_MAX)
> +                       a = SI5351_MULTISYNTH_A_MAX;
> +
> +               /* find best approximation for b/c = fVCO mod fOUT */
> +               denom = 1000 * 1000;
> +               lltmp = (*parent_rate) % rate;
> +               lltmp *= denom;
> +               do_div(lltmp, rate);
> +               rfrac = (unsigned long)lltmp;
> +
> +               b = 0;
> +               c = 1;
> +               if (rfrac)
> +                       rational_best_approximation(rfrac, denom,
> +                           SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +                           &b, &c);
> +       }
> +
> +       /* recalculate rate by fOUT = fIN / (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= c;
> +       do_div(lltmp, a * c + b);
> +       rate  = (unsigned long)lltmp;
> +
> +       /* calculate parameters */
> +       if (divby4) {
> +               hwdata->params.p3 = 1;
> +               hwdata->params.p2 = 0;
> +               hwdata->params.p1 = 0;
> +       } else {
> +               hwdata->params.p3  = c;
> +               hwdata->params.p2  = (128 * b) % c;
> +               hwdata->params.p1  = 128 * a;
> +               hwdata->params.p1 += (128 * b / c);
> +               hwdata->params.p1 -= 512;
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
> +               rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +       int divby4 = 0;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* enable/disable integer mode and divby4 on multisynth0-5 */
> +       if (hwdata->num < 6) {
> +               si5351_set_bits(hwdata->drvdata, reg + 2,
> +                               SI5351_OUTPUT_CLK_DIVBY4,
> +                               (divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INTEGER_MODE,
> +                       (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               divby4, parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +       .set_parent = si5351_msynth_set_parent,
> +       .get_parent = si5351_msynth_get_parent,
> +       .recalc_rate = si5351_msynth_recalc_rate,
> +       .round_rate = si5351_msynth_round_rate,
> +       .set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
> +                                    unsigned char num, unsigned char drive)
> +{
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       switch (drive) {
> +       case 2:
> +               drive = SI5351_CLK_DRIVE_2MA;
> +               break;
> +       case 4:
> +               drive = SI5351_CLK_DRIVE_4MA;
> +               break;
> +       case 6:
> +               drive = SI5351_CLK_DRIVE_6MA;
> +               break;
> +       case 8:
> +               drive = SI5351_CLK_DRIVE_8MA;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_DRIVE_MASK, drive);
> +
> +       return 0;
> +}
> +
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +                                 unsigned char num, unsigned char parent)
> +{
> +       struct clk *pclk;
> +
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +       switch (parent) {
> +       case 0:
> +               pclk = drvdata->msynth[num].hw.clk;
> +               drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +               break;
> +       case 1:
> +               pclk = drvdata->msynth[0].hw.clk;
> +               if (num >= 4)
> +                       pclk = drvdata->msynth[4].hw.clk;
> +               break;
> +       case 2:
> +               pclk = drvdata->xtal.clk;
> +               break;
> +       case 3:
> +               if (drvdata->variant != SI5351_VARIANT_C)
> +                       return -EINVAL;
> +               pclk = drvdata->clkin.clk;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +       return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), 0);
> +       return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       int index = 0;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +       switch (val & SI5351_CLK_INPUT_MASK) {
> +       case SI5351_CLK_INPUT_MULTISYNTH_N:
> +               index = 0;
> +               break;
> +       case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +               index = 1;
> +               break;
> +       case SI5351_CLK_INPUT_XTAL:
> +               index = 2;
> +               break;
> +       case SI5351_CLK_INPUT_CLKIN:
> +               index = 3;
> +               break;
> +       }
> +
> +       return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned val;
> +
> +       val = 0;
> +       hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +       switch (index) {
> +       case 0:
> +               hw->clk->flags |= CLK_SET_RATE_PARENT;
> +               val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               break;
> +       case 1:
> +               /* clk0/clk4 can only connect to its own multisync */
> +               if (hwdata->num == 0 || hwdata->num == 4)
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               else
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +               break;
> +       case 2:
> +               val = SI5351_CLK_INPUT_XTAL;
> +               break;
> +       case 3:
> +               val = SI5351_CLK_INPUT_CLKIN;
> +               break;
> +       }
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INPUT_MASK, val);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg;
> +       unsigned char rdiv;
> +
> +       if (hwdata->num > 5)
> +               reg = si5351_msynth_params_address(hwdata->num) + 2;
> +       else
> +               reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +       rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +       if (hwdata->num == 6) {
> +               rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +       } else {
> +               rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +               rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +       }
> +
> +       return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char rdiv;
> +
> +       /* clkout6/7 can only handle output freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +               rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +       /* clkout freqency is 8kHz - 160MHz */
> +       if (rate > SI5351_CLKOUT_MAX_FREQ)
> +               rate = SI5351_CLKOUT_MAX_FREQ;
> +       if (rate < SI5351_CLKOUT_MIN_FREQ)
> +               rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +       /* request frequency if multisync master */
> +       if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +               /* use r divider for frequencies below 1MHz */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +                      rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +                       rdiv += 1;
> +                       rate *= 2;
> +               }
> +               *parent_rate = rate;
> +       } else {
> +               unsigned long new_rate, new_err, err;
> +
> +               /* round to closed rdiv */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               new_rate = *parent_rate;
> +               err = abs(new_rate - rate);
> +               do {
> +                       new_rate >>= 1;
> +                       new_err = abs(new_rate - rate);
> +                       if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                               break;
> +                       rdiv++;
> +                       err = new_err;
> +               } while (1);
> +       }
> +       rate = *parent_rate >> rdiv;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +               rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long new_rate, new_err, err;
> +       unsigned char rdiv;
> +
> +       /* round to closed rdiv */
> +       rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +       new_rate = parent_rate;
> +       err = abs(new_rate - rate);
> +       do {
> +               new_rate >>= 1;
> +               new_err = abs(new_rate - rate);
> +               if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                       break;
> +               rdiv++;
> +               err = new_err;
> +       } while (1);
> +
> +       /* powerdown clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +       /* write output divider */
> +       switch (hwdata->num) {
> +       case 6:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +               break;
> +       case 7:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +               break;
> +       default:
> +               si5351_set_bits(hwdata->drvdata,
> +                               si5351_msynth_params_address(hwdata->num) + 2,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +       }
> +
> +       /* powerup clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +       .prepare = si5351_clkout_prepare,
> +       .unprepare = si5351_clkout_unprepare,
> +       .set_parent = si5351_clkout_set_parent,
> +       .get_parent = si5351_clkout_get_parent,
> +       .recalc_rate = si5351_clkout_recalc_rate,
> +       .round_rate = si5351_clkout_round_rate,
> +       .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +       struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +       struct device_node *np = client->dev.of_node;
> +       struct property *prop;
> +       const __be32 *p;
> +       unsigned int num, val;
> +
> +       if (np == NULL)
> +               return;
> +
> +       /*
> +        * property pll-source : <num src>, [<..>]
> +        * allow to selectively set pll source
> +        */
> +       of_property_for_each_u32(client->dev.of_node, "pll-source",
> +                                prop, p, num) {
> +               if (num >= 2) {
> +                       dev_err(&client->dev,
> +                               "invalid pll %d on pll-source prop\n", num);
> +                       break;
> +               }
> +
> +               p = of_prop_next_u32(prop, p, &val);
> +               if (!p)
> +                       break;
> +
> +               if (_si5351_pll_reparent(drvdata, num, val))
> +                       dev_warn(&client->dev,
> +                                "unable to reparent pll %d to %d\n",
> +                                num, val);
> +       }
> +
> +       for_each_child_of_node(client->dev.of_node, np) {
> +               if (of_property_read_u32(np, "reg", &num)) {
> +                       dev_err(&client->dev, "missing reg property of %s\n",
> +                               np->full_name);
> +                       continue;
> +               }
> +
> +               if (of_property_read_bool(np, "pll-master"))
> +                       _si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +               if (!of_property_read_u32(np, "drive-strength", &val)) {
> +                       if (_si5351_clkout_set_drive_strength(drvdata,
> +                                                             num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to set drive strength of %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +                       if (_si5351_msynth_reparent(drvdata, num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to reparent multisynth %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "clock-source", &val)) {
> +                       if (_si5351_clkout_reparent(drvdata, num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to reparent clockout %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "clock-frequency", &val))
> +                       clk_set_rate(drvdata->onecell.clks[num], val);
> +       }
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +       { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +       { .compatible = "silabs,si5351a-msop",
> +                                        .data = (void *)SI5351_VARIANT_A3, },
> +       { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +       { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +       struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +       struct device_node *np = client->dev.of_node;
> +       const struct of_device_id *match;
> +
> +       if (np == NULL)
> +               return -EINVAL;
> +
> +       match = of_match_node(si5351_dt_ids, np);
> +       if (match == NULL)
> +               return -EINVAL;
> +
> +       drvdata->variant = (enum si5351_variant)match->data;
> +       drvdata->pxtal = of_clk_get(np, 0);
> +       drvdata->pclkin = of_clk_get(np, 1);
> +
> +       return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +       struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +       struct si5351_driver_data *drvdata;
> +       struct clk_init_data init;
> +       struct clk *clk;
> +       const char *parent_names[4];
> +       u8 num_parents, num_clocks;
> +       int ret, n;
> +
> +       drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +                              GFP_KERNEL);
> +       if (drvdata == NULL) {
> +               dev_err(&client->dev, "unable to allocate driver data\n");
> +               return -ENOMEM;
> +       }
> +
> +       ret = si5351_dt_parse(client, drvdata);
> +       if (ret)
> +               return ret;
> +
> +       i2c_set_clientdata(client, drvdata);
> +       drvdata->client = client;
> +       drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +       if (IS_ERR(drvdata->regmap)) {
> +               dev_err(&client->dev, "failed to allocate register map\n");
> +               return PTR_ERR(drvdata->regmap);
> +       }
> +
> +       /* Disable interrupts */
> +       si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +       /* Set disabled output drivers to drive low */
> +       si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +       si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +       /* Ensure pll select is on XTAL for Si5351A/B */
> +       if (drvdata->variant != SI5351_VARIANT_C)
> +               si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                               SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +       /* register xtal input clock gate */
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       init.name = si5351_input_names[0];
> +       init.ops = &si5351_xtal_ops;
> +       init.flags = 0;
> +       if (!IS_ERR(drvdata->pxtal)) {
> +               init.parent_names = &drvdata->pxtal->name;
> +               init.num_parents = 1;
> +       }
> +       drvdata->xtal.init = &init;
> +       clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return PTR_ERR(clk);
> +       }
> +
> +       /* register clkin input clock gate */
> +       if (drvdata->variant == SI5351_VARIANT_C) {
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_input_names[1];
> +               init.ops = &si5351_clkin_ops;
> +               if (!IS_ERR(drvdata->pclkin)) {
> +                       init.parent_names = &drvdata->pclkin->name;
> +                       init.num_parents = 1;
> +               }
> +               drvdata->clkin.init = &init;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return PTR_ERR(clk);
> +               }
> +       }
> +
> +       /* Si5351C allows to mux either xtal or clkin to PLL input */
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +       parent_names[0] = si5351_input_names[0];
> +       parent_names[1] = si5351_input_names[1];
> +
> +       /* register PLLA */
> +       drvdata->pll[0].num = 0;
> +       drvdata->pll[0].drvdata = drvdata;
> +       drvdata->pll[0].hw.init = &init;
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       init.name = si5351_pll_names[0];
> +       init.ops = &si5351_pll_ops;
> +       init.flags = 0;
> +       init.parent_names = parent_names;
> +       init.num_parents = num_parents;
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register PLLB or VXCO (Si5351B) */
> +       drvdata->pll[1].num = 1;
> +       drvdata->pll[1].drvdata = drvdata;
> +       drvdata->pll[1].hw.init = &init;
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       if (drvdata->variant == SI5351_VARIANT_B) {
> +               init.name = si5351_pll_names[2];
> +               init.ops = &si5351_vxco_ops;
> +               init.flags = CLK_IS_ROOT;
> +               init.parent_names = NULL;
> +               init.num_parents = 0;
> +       } else {
> +               init.name = si5351_pll_names[1];
> +               init.ops = &si5351_pll_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +       }
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register clk multisync and clk out divider */
> +       num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +       parent_names[0] = si5351_pll_names[0];
> +       if (drvdata->variant == SI5351_VARIANT_B)
> +               parent_names[1] = si5351_pll_names[2];
> +       else
> +               parent_names[1] = si5351_pll_names[1];
> +
> +       drvdata->msynth = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +       drvdata->clkout = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +       drvdata->onecell.clk_num = num_clocks;
> +       drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +       if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +                   !drvdata->onecell.clks))
> +               return -ENOMEM;
> +
> +       for (n = 0; n < num_clocks; n++) {
> +               drvdata->msynth[n].num = n;
> +               drvdata->msynth[n].drvdata = drvdata;
> +               drvdata->msynth[n].hw.init = &init;
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_msynth_names[n];
> +               init.ops = &si5351_msynth_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = 2;
> +               clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +       parent_names[2] = si5351_input_names[0];
> +       parent_names[3] = si5351_input_names[1];
> +       for (n = 0; n < num_clocks; n++) {
> +               parent_names[0] = si5351_msynth_names[n];
> +               parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +                       si5351_msynth_names[4];
> +
> +               drvdata->clkout[n].num = n;
> +               drvdata->clkout[n].drvdata = drvdata;
> +               drvdata->clkout[n].hw.init = &init;
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_clkout_names[n];
> +               init.ops = &si5351_clkout_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +               drvdata->onecell.clks[n] = clk;
> +       }
> +
> +       /* setup clock setup from DT */
> +       si5351_dt_setup(client, drvdata);
> +
> +       of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +                           &drvdata->onecell);
> +
> +       dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +       return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +       i2c_set_clientdata(client, NULL);
> +       return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +       { "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +       { "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +       .driver = {
> +               .name = "si5351",
> +               .of_match_table = si5351_dt_ids,
> +       },
> +       .probe = si5351_i2c_probe,
> +       .remove = si5351_i2c_remove,
> +       .id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +       return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +       i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR                   0x60
> +
> +#define SI5351_PLL_VCO_MIN                     600000000
> +#define SI5351_PLL_VCO_MAX                     900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ             1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ          150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ             160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ           SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ                 8000
> +#define SI5351_CLKOUT_MAX_FREQ                 SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ               SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN                       15
> +#define SI5351_PLL_A_MAX                       90
> +#define SI5351_PLL_B_MAX                       (SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX                       1048575
> +#define SI5351_MULTISYNTH_A_MIN                        6
> +#define SI5351_MULTISYNTH_A_MAX                        1800
> +#define SI5351_MULTISYNTH67_A_MAX              254
> +#define SI5351_MULTISYNTH_B_MAX                        (SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX                        1048575
> +#define SI5351_MULTISYNTH_P1_MAX               ((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX               ((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX               ((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS                   0
> +#define SI5351_INTERRUPT_STATUS                        1
> +#define SI5351_INTERRUPT_MASK                  2
> +#define  SI5351_STATUS_SYS_INIT                        (1<<7)
> +#define  SI5351_STATUS_LOL_B                   (1<<6)
> +#define  SI5351_STATUS_LOL_A                   (1<<5)
> +#define  SI5351_STATUS_LOS                     (1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL              3
> +#define SI5351_OEB_PIN_ENABLE_CTRL             9
> +#define SI5351_PLL_INPUT_SOURCE                        15
> +#define  SI5351_CLKIN_DIV_MASK                 (3<<6)
> +#define  SI5351_CLKIN_DIV_1                    (0<<6)
> +#define  SI5351_CLKIN_DIV_2                    (1<<6)
> +#define  SI5351_CLKIN_DIV_4                    (2<<6)
> +#define  SI5351_CLKIN_DIV_8                    (3<<6)
> +#define  SI5351_PLLB_SOURCE                    (1<<3)
> +#define  SI5351_PLLA_SOURCE                    (1<<2)
> +
> +#define SI5351_CLK0_CTRL                       16
> +#define SI5351_CLK1_CTRL                       17
> +#define SI5351_CLK2_CTRL                       18
> +#define SI5351_CLK3_CTRL                       19
> +#define SI5351_CLK4_CTRL                       20
> +#define SI5351_CLK5_CTRL                       21
> +#define SI5351_CLK6_CTRL                       22
> +#define SI5351_CLK7_CTRL                       23
> +#define  SI5351_CLK_POWERDOWN                  (1<<7)
> +#define  SI5351_CLK_INTEGER_MODE               (1<<6)
> +#define  SI5351_CLK_PLL_SELECT                 (1<<5)
> +#define  SI5351_CLK_INVERT                     (1<<4)
> +#define  SI5351_CLK_INPUT_MASK                 (3<<2)
> +#define  SI5351_CLK_INPUT_XTAL                 (0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN                        (1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4       (2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N         (3<<2)
> +#define  SI5351_CLK_DRIVE_MASK                 (3<<0)
> +#define  SI5351_CLK_DRIVE_2MA                  (0<<0)
> +#define  SI5351_CLK_DRIVE_4MA                  (1<<0)
> +#define  SI5351_CLK_DRIVE_6MA                  (2<<0)
> +#define  SI5351_CLK_DRIVE_8MA                  (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE            24
> +#define SI5351_CLK7_4_DISABLE_STATE            25
> +#define  SI5351_CLK_DISABLE_STATE_LOW          0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH         1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT                2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER                3
> +
> +#define SI5351_PARAMETERS_LENGTH               8
> +#define SI5351_PLLA_PARAMETERS                 26
> +#define SI5351_PLLB_PARAMETERS                 34
> +#define SI5351_CLK0_PARAMETERS                 42
> +#define SI5351_CLK1_PARAMETERS                 50
> +#define SI5351_CLK2_PARAMETERS                 58
> +#define SI5351_CLK3_PARAMETERS                 66
> +#define SI5351_CLK4_PARAMETERS                 74
> +#define SI5351_CLK5_PARAMETERS                 82
> +#define SI5351_CLK6_PARAMETERS                 90
> +#define SI5351_CLK7_PARAMETERS                 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER           92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK            (7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK           (7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT           4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT          0
> +#define  SI5351_OUTPUT_CLK_DIV_1               0
> +#define  SI5351_OUTPUT_CLK_DIV_2               1
> +#define  SI5351_OUTPUT_CLK_DIV_4               2
> +#define  SI5351_OUTPUT_CLK_DIV_8               3
> +#define  SI5351_OUTPUT_CLK_DIV_16              4
> +#define  SI5351_OUTPUT_CLK_DIV_32              5
> +#define  SI5351_OUTPUT_CLK_DIV_64              6
> +#define  SI5351_OUTPUT_CLK_DIV_128             7
> +#define  SI5351_OUTPUT_CLK_DIVBY4              (3<<2)
> +
> +#define SI5351_SSC_PARAM0                      149
> +#define SI5351_SSC_PARAM1                      150
> +#define SI5351_SSC_PARAM2                      151
> +#define SI5351_SSC_PARAM3                      152
> +#define SI5351_SSC_PARAM4                      153
> +#define SI5351_SSC_PARAM5                      154
> +#define SI5351_SSC_PARAM6                      155
> +#define SI5351_SSC_PARAM7                      156
> +#define SI5351_SSC_PARAM8                      157
> +#define SI5351_SSC_PARAM9                      158
> +#define SI5351_SSC_PARAM10                     159
> +#define SI5351_SSC_PARAM11                     160
> +#define SI5351_SSC_PARAM12                     161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW             162
> +#define SI5351_VXCO_PARAMETERS_MID             163
> +#define SI5351_VXCO_PARAMETERS_HIGH            164
> +
> +#define SI5351_CLK0_PHASE_OFFSET               165
> +#define SI5351_CLK1_PHASE_OFFSET               166
> +#define SI5351_CLK2_PHASE_OFFSET               167
> +#define SI5351_CLK3_PHASE_OFFSET               168
> +#define SI5351_CLK4_PHASE_OFFSET               169
> +#define SI5351_CLK5_PHASE_OFFSET               170
> +
> +#define SI5351_PLL_RESET                       177
> +#define  SI5351_PLL_RESET_B                    (1<<7)
> +#define  SI5351_PLL_RESET_A                    (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD                    183
> +#define  SI5351_CRYSTAL_LOAD_MASK              (3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF               (1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF               (2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF              (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE                   187
> +#define  SI5351_CLKIN_ENABLE                   (1<<7)
> +#define  SI5351_XTAL_ENABLE                    (1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE              (1<<4)
> +
> +#endif
> -- 
> 1.7.10.4

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-20  0:26     ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-03-20  0:26 UTC (permalink / raw)
  To: linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-03-18 03:43:17)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 

I've just pushed out a new clk-next branch which includes Ulf Hansson's
series to introduce the .is_prepared callback.  Perhaps you can use that
where you were previously using .is_enabled?

Regards,
Mike

> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: devicetree-discuss at lists.ozlabs.org
> Cc: linux-doc at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-arm-kernel at lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1709 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..f73d2d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +       compatible = "fixed-clock";
> +       #clock-cells = <0>;
> +       clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +       /* Si5351a msop10 i2c clock generator */
> +       si5351a: clock-generator at 60 {
> +               compatible = "silabs,si5351a-msop";
> +               reg = <0x60>;
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +               #clock-cells = <1>;
> +
> +               /* connect xtal input to 25MHz reference */
> +               clocks = <&ref25>;
> +
> +               /* connect xtal input as source of pll0 and pll1 */
> +               pll-source = <0 0>, <1 0>;
> +
> +               /*
> +                * overwrite clkout0 configuration with:
> +                * - 8mA output drive strength
> +                * - pll0 as clock source of multisynth0
> +                * - multisynth0 as clock source of output divider
> +                * - multisynth0 can change pll0
> +                * - set initial clock frequency of 74.25MHz
> +                */
> +               clkout0 {
> +                       reg = <0>;
> +                       drive-strength = <8>;
> +                       multisynth-source = <0>;
> +                       clock-source = <0>;
> +                       pll-master;
> +                       clock-frequency = <74250000>;
> +               };
> +
> +               /*
> +                * overwrite clkout1 configuration with:
> +                * - 4mA output drive strength
> +                * - pll1 as clock source of multisynth1
> +                * - multisynth1 as clock source of output divider
> +                * - multisynth1 can change pll1
> +                */
> +               clkout1 {
> +                       reg = <1>;
> +                       drive-strength = <4>;
> +                       multisynth-source = <1>;
> +                       clock-source = <0>;
> +                       pll-master;
> +               };
> +
> +               /*
> +                * overwrite clkout2 configuration with:
> +                * - xtal as clock source of output divider
> +                */
> +               clkout2 {
> +                       reg = <2>;
> +                       clock-source = <2>;
> +               };
> +       };
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung       Samsung Semiconductor
>  sbs    Smart Battery System
>  schindler      Schindler
>  sil    Silicon Image
> +silabs Silicon Laboratories
>  simtek
>  sirf   SiRF Technology, Inc.
>  snps   Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index e23e471..aba4f59 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -61,6 +61,15 @@ config COMMON_CLK_MAX77686
>         ---help---
>           This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +       tristate "Clock driver for SiLabs 5351A/B/C"
> +       depends on I2C
> +       select REGMAP_I2C
> +       select RATIONAL
> +       ---help---
> +         This driver supports Silicon Labs 5351A/B/C programmable clock
> +         generators.
> +
>  config CLK_TWL6040
>         tristate "External McPDM functional clock from twl6040"
>         depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 095899e..8f58889 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_X86)             += x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)      += clk-twl6040.o
>  
>  # Debug drivers
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..38540e7
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1429 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>
> +#include <linux/clkdev.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +enum si5351_variant {
> +       SI5351_VARIANT_A = 1,
> +       SI5351_VARIANT_A3 = 2,
> +       SI5351_VARIANT_B = 3,
> +       SI5351_VARIANT_C = 4,
> +};
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +       unsigned long   p1;
> +       unsigned long   p2;
> +       unsigned long   p3;
> +       int             valid;
> +};
> +
> +struct si5351_hw_data {
> +       struct clk_hw                   hw;
> +       struct si5351_driver_data       *drvdata;
> +       struct si5351_parameters        params;
> +       unsigned char                   num;
> +};
> +
> +struct si5351_driver_data {
> +       enum si5351_variant     variant;
> +       struct i2c_client       *client;
> +       struct regmap           *regmap;
> +       struct clk_onecell_data onecell;
> +
> +       struct clk              *pxtal;
> +       struct clk_hw           xtal;
> +       struct clk              *pclkin;
> +       struct clk_hw           clkin;
> +
> +       struct si5351_hw_data   pll[2];
> +       struct si5351_hw_data   *msynth;
> +       struct si5351_hw_data   *clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +       "xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +       "plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +       "ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +       "clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
> +       unsigned char reg)
> +{
> +       unsigned int val;
> +       int ret;
> +
> +       ret = regmap_read(drvdata->regmap, reg, &val);
> +       if (ret) {
> +               dev_err(&drvdata->client->dev,
> +                       "unable to read from reg%02x\n", reg);
> +               return 0;
> +       }
> +
> +       return (unsigned char)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char count, unsigned char *buf)
> +{
> +       return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char val)
> +{
> +       return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char count, const unsigned char *buf)
> +{
> +       return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +       unsigned char reg, unsigned char mask, unsigned char val)
> +{
> +       return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline unsigned char si5351_msynth_params_address(int num)
> +{
> +       if (num > 5)
> +               return SI5351_CLK6_PARAMETERS + (num - 6);
> +       return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +       unsigned char reg, struct si5351_parameters *params)
> +{
> +       unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = si5351_reg_read(drvdata, reg);
> +               params->p1 = buf[0];
> +               params->p2 = 0;
> +               params->p3 = 1;
> +               break;
> +       default:
> +               si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +               params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +               params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +               params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +       }
> +       params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +       unsigned char reg, struct si5351_parameters *params)
> +{
> +       unsigned char buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = params->p1 & 0xff;
> +               si5351_reg_write(drvdata, reg, buf[0]);
> +               break;
> +       default:
> +               buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +               buf[1] = params->p3 & 0xff;
> +               /* save rdiv and divby4 */
> +               buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +               buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +               buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +               buf[4] = params->p1 & 0xff;
> +               buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +                       ((params->p2 & 0xf0000) >> 16);
> +               buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +               buf[7] = params->p2 & 0xff;
> +               si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +       }
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +       switch (reg) {
> +       case SI5351_DEVICE_STATUS:
> +       case SI5351_INTERRUPT_STATUS:
> +       case SI5351_PLL_RESET:
> +               return true;
> +       }
> +       return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +       /* reserved registers */
> +       if (reg >= 4 && reg <= 8)
> +               return false;
> +       if (reg >= 10 && reg <= 14)
> +               return false;
> +       if (reg >= 173 && reg <= 176)
> +               return false;
> +       if (reg >= 178 && reg <= 182)
> +               return false;
> +       /* read-only */
> +       if (reg == SI5351_DEVICE_STATUS)
> +               return false;
> +       return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +       .reg_bits = 8,
> +       .val_bits = 8,
> +       .cache_type = REGCACHE_RBTREE,
> +       .max_register = 187,
> +       .writeable_reg = si5351_regmap_is_writeable,
> +       .volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +       .prepare = si5351_xtal_prepare,
> +       .unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +                                             unsigned long parent_rate)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       unsigned long rate;
> +       unsigned char idiv;
> +
> +       rate = parent_rate;
> +       if (parent_rate > 160000000) {
> +               idiv = SI5351_CLKIN_DIV_8;
> +               rate /= 8;
> +       } else if (parent_rate > 80000000) {
> +               idiv = SI5351_CLKIN_DIV_4;
> +               rate /= 4;
> +       } else if (parent_rate > 40000000) {
> +               idiv = SI5351_CLKIN_DIV_2;
> +               rate /= 2;
> +       } else {
> +               idiv = SI5351_CLKIN_DIV_1;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       SI5351_CLKIN_DIV_MASK, idiv);
> +
> +       dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +               __func__, (1 << (idiv >> 6)), rate);
> +
> +       return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +       .prepare = si5351_clkin_prepare,
> +       .unprepare = si5351_clkin_unprepare,
> +       .recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +       return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +                                            unsigned long parent_rate)
> +{
> +       return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long parent)
> +{
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +       .prepare = si5351_vxco_prepare,
> +       .unprepare = si5351_vxco_unprepare,
> +       .recalc_rate = si5351_vxco_recalc_rate,
> +       .set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +                                      unsigned char num, unsigned char parent)
> +{
> +       if (num > 2 ||
> +           (drvdata->variant == SI5351_VARIANT_B && num > 1))
> +               return -EINVAL;
> +
> +       if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
> +               return -EINVAL;
> +
> +       return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
> +                             drvdata->clkin.clk : drvdata->xtal.clk);
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char mask = (hwdata->num == 0) ?
> +               SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +       return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char mask = (hwdata->num == 0) ?
> +               SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +       if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +           index > 0)
> +               return -EPERM;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       mask, (index) ? 0 : mask);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = (hwdata->num == 0) ?
> +               SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +       unsigned long long rate;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +       rate  = hwdata->params.p1 * hwdata->params.p3;
> +       rate += 512 * hwdata->params.p3;
> +       rate += hwdata->params.p2;
> +       rate *= parent_rate;
> +       do_div(rate, 128 * hwdata->params.p3);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long rfrac, denom, a, b, c;
> +       unsigned long long lltmp;
> +
> +       if (rate < SI5351_PLL_VCO_MIN)
> +               rate = SI5351_PLL_VCO_MIN;
> +       if (rate > SI5351_PLL_VCO_MAX)
> +               rate = SI5351_PLL_VCO_MAX;
> +
> +       /* determine integer part of feedback equation */
> +       a = rate / *parent_rate;
> +
> +       if (a < SI5351_PLL_A_MIN)
> +               rate = *parent_rate * SI5351_PLL_A_MIN;
> +       if (a > SI5351_PLL_A_MAX)
> +               rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +       /* find best approximation for b/c = fVCO mod fIN */
> +       denom = 1000 * 1000;
> +       lltmp = rate % (*parent_rate);
> +       lltmp *= denom;
> +       do_div(lltmp, *parent_rate);
> +       rfrac = (unsigned long)lltmp;
> +
> +       b = 0;
> +       c = 1;
> +       if (rfrac)
> +               rational_best_approximation(rfrac, denom,
> +                                   SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +       /* calculate parameters */
> +       hwdata->params.p3  = c;
> +       hwdata->params.p2  = (128 * b) % c;
> +       hwdata->params.p1  = 128 * a;
> +       hwdata->params.p1 += (128 * b / c);
> +       hwdata->params.p1 -= 512;
> +
> +       /* recalculate rate by fIN * (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= b;
> +       do_div(lltmp, c);
> +
> +       rate  = (unsigned long)lltmp;
> +       rate += *parent_rate * a;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = (hwdata->num == 0) ?
> +               SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
> +       unsigned char val;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       /* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +               SI5351_CLK_INTEGER_MODE,
> +               (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +       /* reset pll */
> +       val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
> +       si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +       .set_parent = si5351_pll_set_parent,
> +       .get_parent = si5351_pll_get_parent,
> +       .recalc_rate = si5351_pll_recalc_rate,
> +       .round_rate = si5351_pll_round_rate,
> +       .set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static inline void _si5351_msynth_set_pll_master(
> +       struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> +{
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return;
> +
> +       if (is_master)
> +               drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +       else
> +               drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +}
> +
> +static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +                                 unsigned char num, unsigned char parent)
> +{
> +       if (parent > 2)
> +               return -EINVAL;
> +
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       return clk_set_parent(drvdata->msynth[num].hw.clk,
> +                             drvdata->pll[parent].hw.clk);
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +       return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_PLL_SELECT,
> +                       (index) ? SI5351_CLK_PLL_SELECT : 0);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +       unsigned long long rate;
> +       unsigned long m;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /*
> +        * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +        * multisync6-7: fOUT = fIN / P1
> +        */
> +       rate = parent_rate;
> +       if (hwdata->num > 5) {
> +               m = hwdata->params.p1;
> +       } else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +                   SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +               m = 4;
> +       } else {
> +               rate *= 128 * hwdata->params.p3;
> +               m = hwdata->params.p1 * hwdata->params.p3;
> +               m += hwdata->params.p2;
> +               m += 512 * hwdata->params.p3;
> +       }
> +
> +       if (m == 0)
> +               return 0;
> +       do_div(rate, m);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               m, parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long long lltmp;
> +       unsigned long a, b, c;
> +       int divby4;
> +
> +       /* multisync6-7 can only handle freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +       /* multisync frequency is 1MHz .. 160MHz */
> +       if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH_MAX_FREQ;
> +       if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +               rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +       divby4 = 0;
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* multisync can set pll */
> +       if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +               /*
> +                * find largest integer divider for max
> +                * vco frequency and given target rate
> +                */
> +               if (divby4 == 0) {
> +                       lltmp = SI5351_PLL_VCO_MAX;
> +                       do_div(lltmp, rate);
> +                       a = (unsigned long)lltmp;
> +               } else
> +                       a = 4;
> +
> +               b = 0;
> +               c = 1;
> +
> +               *parent_rate = a * rate;
> +       } else {
> +               unsigned long rfrac, denom;
> +
> +               /* disable divby4 */
> +               if (divby4) {
> +                       rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +                       divby4 = 0;
> +               }
> +
> +               /* determine integer part of divider equation */
> +               a = *parent_rate / rate;
> +               if (a < SI5351_MULTISYNTH_A_MIN)
> +                       a = SI5351_MULTISYNTH_A_MIN;
> +               if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +                       a = SI5351_MULTISYNTH67_A_MAX;
> +               else if (a > SI5351_MULTISYNTH_A_MAX)
> +                       a = SI5351_MULTISYNTH_A_MAX;
> +
> +               /* find best approximation for b/c = fVCO mod fOUT */
> +               denom = 1000 * 1000;
> +               lltmp = (*parent_rate) % rate;
> +               lltmp *= denom;
> +               do_div(lltmp, rate);
> +               rfrac = (unsigned long)lltmp;
> +
> +               b = 0;
> +               c = 1;
> +               if (rfrac)
> +                       rational_best_approximation(rfrac, denom,
> +                           SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +                           &b, &c);
> +       }
> +
> +       /* recalculate rate by fOUT = fIN / (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= c;
> +       do_div(lltmp, a * c + b);
> +       rate  = (unsigned long)lltmp;
> +
> +       /* calculate parameters */
> +       if (divby4) {
> +               hwdata->params.p3 = 1;
> +               hwdata->params.p2 = 0;
> +               hwdata->params.p1 = 0;
> +       } else {
> +               hwdata->params.p3  = c;
> +               hwdata->params.p2  = (128 * b) % c;
> +               hwdata->params.p1  = 128 * a;
> +               hwdata->params.p1 += (128 * b / c);
> +               hwdata->params.p1 -= 512;
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
> +               rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg = si5351_msynth_params_address(hwdata->num);
> +       int divby4 = 0;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* enable/disable integer mode and divby4 on multisynth0-5 */
> +       if (hwdata->num < 6) {
> +               si5351_set_bits(hwdata->drvdata, reg + 2,
> +                               SI5351_OUTPUT_CLK_DIVBY4,
> +                               (divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INTEGER_MODE,
> +                       (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name,
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               divby4, parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +       .set_parent = si5351_msynth_set_parent,
> +       .get_parent = si5351_msynth_get_parent,
> +       .recalc_rate = si5351_msynth_recalc_rate,
> +       .round_rate = si5351_msynth_round_rate,
> +       .set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
> +                                    unsigned char num, unsigned char drive)
> +{
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       switch (drive) {
> +       case 2:
> +               drive = SI5351_CLK_DRIVE_2MA;
> +               break;
> +       case 4:
> +               drive = SI5351_CLK_DRIVE_4MA;
> +               break;
> +       case 6:
> +               drive = SI5351_CLK_DRIVE_6MA;
> +               break;
> +       case 8:
> +               drive = SI5351_CLK_DRIVE_8MA;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_DRIVE_MASK, drive);
> +
> +       return 0;
> +}
> +
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +                                 unsigned char num, unsigned char parent)
> +{
> +       struct clk *pclk;
> +
> +       if (num > 8 ||
> +           (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +               return -EINVAL;
> +
> +       drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
> +       switch (parent) {
> +       case 0:
> +               pclk = drvdata->msynth[num].hw.clk;
> +               drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
> +               break;
> +       case 1:
> +               pclk = drvdata->msynth[0].hw.clk;
> +               if (num >= 4)
> +                       pclk = drvdata->msynth[4].hw.clk;
> +               break;
> +       case 2:
> +               pclk = drvdata->xtal.clk;
> +               break;
> +       case 3:
> +               if (drvdata->variant != SI5351_VARIANT_C)
> +                       return -EINVAL;
> +               pclk = drvdata->clkin.clk;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +       return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), 0);
> +       return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       int index = 0;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +       switch (val & SI5351_CLK_INPUT_MASK) {
> +       case SI5351_CLK_INPUT_MULTISYNTH_N:
> +               index = 0;
> +               break;
> +       case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +               index = 1;
> +               break;
> +       case SI5351_CLK_INPUT_XTAL:
> +               index = 2;
> +               break;
> +       case SI5351_CLK_INPUT_CLKIN:
> +               index = 3;
> +               break;
> +       }
> +
> +       return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned val;
> +
> +       val = 0;
> +       hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> +       switch (index) {
> +       case 0:
> +               hw->clk->flags |= CLK_SET_RATE_PARENT;
> +               val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               break;
> +       case 1:
> +               /* clk0/clk4 can only connect to its own multisync */
> +               if (hwdata->num == 0 || hwdata->num == 4)
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               else
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +               break;
> +       case 2:
> +               val = SI5351_CLK_INPUT_XTAL;
> +               break;
> +       case 3:
> +               val = SI5351_CLK_INPUT_CLKIN;
> +               break;
> +       }
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INPUT_MASK, val);
> +
> +       return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg;
> +       unsigned char rdiv;
> +
> +       if (hwdata->num > 5)
> +               reg = si5351_msynth_params_address(hwdata->num) + 2;
> +       else
> +               reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +       rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +       if (hwdata->num == 6) {
> +               rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +       } else {
> +               rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +               rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +       }
> +
> +       return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char rdiv;
> +
> +       /* clkout6/7 can only handle output freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +               rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +       /* clkout freqency is 8kHz - 160MHz */
> +       if (rate > SI5351_CLKOUT_MAX_FREQ)
> +               rate = SI5351_CLKOUT_MAX_FREQ;
> +       if (rate < SI5351_CLKOUT_MIN_FREQ)
> +               rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +       /* request frequency if multisync master */
> +       if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> +               /* use r divider for frequencies below 1MHz */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +                      rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +                       rdiv += 1;
> +                       rate *= 2;
> +               }
> +               *parent_rate = rate;
> +       } else {
> +               unsigned long new_rate, new_err, err;
> +
> +               /* round to closed rdiv */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               new_rate = *parent_rate;
> +               err = abs(new_rate - rate);
> +               do {
> +                       new_rate >>= 1;
> +                       new_err = abs(new_rate - rate);
> +                       if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                               break;
> +                       rdiv++;
> +                       err = new_err;
> +               } while (1);
> +       }
> +       rate = *parent_rate >> rdiv;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> +               rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long new_rate, new_err, err;
> +       unsigned char rdiv;
> +
> +       /* round to closed rdiv */
> +       rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +       new_rate = parent_rate;
> +       err = abs(new_rate - rate);
> +       do {
> +               new_rate >>= 1;
> +               new_err = abs(new_rate - rate);
> +               if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                       break;
> +               rdiv++;
> +               err = new_err;
> +       } while (1);
> +
> +       /* powerdown clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> +       /* write output divider */
> +       switch (hwdata->num) {
> +       case 6:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +               break;
> +       case 7:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +               break;
> +       default:
> +               si5351_set_bits(hwdata->drvdata,
> +                               si5351_msynth_params_address(hwdata->num) + 2,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +       }
> +
> +       /* powerup clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +       .prepare = si5351_clkout_prepare,
> +       .unprepare = si5351_clkout_unprepare,
> +       .set_parent = si5351_clkout_set_parent,
> +       .get_parent = si5351_clkout_get_parent,
> +       .recalc_rate = si5351_clkout_recalc_rate,
> +       .round_rate = si5351_clkout_round_rate,
> +       .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> +       struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +       struct device_node *np = client->dev.of_node;
> +       struct property *prop;
> +       const __be32 *p;
> +       unsigned int num, val;
> +
> +       if (np == NULL)
> +               return;
> +
> +       /*
> +        * property pll-source : <num src>, [<..>]
> +        * allow to selectively set pll source
> +        */
> +       of_property_for_each_u32(client->dev.of_node, "pll-source",
> +                                prop, p, num) {
> +               if (num >= 2) {
> +                       dev_err(&client->dev,
> +                               "invalid pll %d on pll-source prop\n", num);
> +                       break;
> +               }
> +
> +               p = of_prop_next_u32(prop, p, &val);
> +               if (!p)
> +                       break;
> +
> +               if (_si5351_pll_reparent(drvdata, num, val))
> +                       dev_warn(&client->dev,
> +                                "unable to reparent pll %d to %d\n",
> +                                num, val);
> +       }
> +
> +       for_each_child_of_node(client->dev.of_node, np) {
> +               if (of_property_read_u32(np, "reg", &num)) {
> +                       dev_err(&client->dev, "missing reg property of %s\n",
> +                               np->full_name);
> +                       continue;
> +               }
> +
> +               if (of_property_read_bool(np, "pll-master"))
> +                       _si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> +               if (!of_property_read_u32(np, "drive-strength", &val)) {
> +                       if (_si5351_clkout_set_drive_strength(drvdata,
> +                                                             num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to set drive strength of %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "multisynth-source", &val)) {
> +                       if (_si5351_msynth_reparent(drvdata, num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to reparent multisynth %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "clock-source", &val)) {
> +                       if (_si5351_clkout_reparent(drvdata, num, val))
> +                               dev_warn(&client->dev,
> +                                        "unable to reparent clockout %d to %d\n",
> +                                        num, val);
> +               }
> +
> +               if (!of_property_read_u32(np, "clock-frequency", &val))
> +                       clk_set_rate(drvdata->onecell.clks[num], val);
> +       }
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +       { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +       { .compatible = "silabs,si5351a-msop",
> +                                        .data = (void *)SI5351_VARIANT_A3, },
> +       { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +       { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> +       struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> +       struct device_node *np = client->dev.of_node;
> +       const struct of_device_id *match;
> +
> +       if (np == NULL)
> +               return -EINVAL;
> +
> +       match = of_match_node(si5351_dt_ids, np);
> +       if (match == NULL)
> +               return -EINVAL;
> +
> +       drvdata->variant = (enum si5351_variant)match->data;
> +       drvdata->pxtal = of_clk_get(np, 0);
> +       drvdata->pclkin = of_clk_get(np, 1);
> +
> +       return 0;
> +}
> +
> +static int si5351_i2c_probe(
> +       struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +       struct si5351_driver_data *drvdata;
> +       struct clk_init_data init;
> +       struct clk *clk;
> +       const char *parent_names[4];
> +       u8 num_parents, num_clocks;
> +       int ret, n;
> +
> +       drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> +                              GFP_KERNEL);
> +       if (drvdata == NULL) {
> +               dev_err(&client->dev, "unable to allocate driver data\n");
> +               return -ENOMEM;
> +       }
> +
> +       ret = si5351_dt_parse(client, drvdata);
> +       if (ret)
> +               return ret;
> +
> +       i2c_set_clientdata(client, drvdata);
> +       drvdata->client = client;
> +       drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +       if (IS_ERR(drvdata->regmap)) {
> +               dev_err(&client->dev, "failed to allocate register map\n");
> +               return PTR_ERR(drvdata->regmap);
> +       }
> +
> +       /* Disable interrupts */
> +       si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +       /* Set disabled output drivers to drive low */
> +       si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +       si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +       /* Ensure pll select is on XTAL for Si5351A/B */
> +       if (drvdata->variant != SI5351_VARIANT_C)
> +               si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                               SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +       /* register xtal input clock gate */
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       init.name = si5351_input_names[0];
> +       init.ops = &si5351_xtal_ops;
> +       init.flags = 0;
> +       if (!IS_ERR(drvdata->pxtal)) {
> +               init.parent_names = &drvdata->pxtal->name;
> +               init.num_parents = 1;
> +       }
> +       drvdata->xtal.init = &init;
> +       clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return PTR_ERR(clk);
> +       }
> +
> +       /* register clkin input clock gate */
> +       if (drvdata->variant == SI5351_VARIANT_C) {
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_input_names[1];
> +               init.ops = &si5351_clkin_ops;
> +               if (!IS_ERR(drvdata->pclkin)) {
> +                       init.parent_names = &drvdata->pclkin->name;
> +                       init.num_parents = 1;
> +               }
> +               drvdata->clkin.init = &init;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return PTR_ERR(clk);
> +               }
> +       }
> +
> +       /* Si5351C allows to mux either xtal or clkin to PLL input */
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +       parent_names[0] = si5351_input_names[0];
> +       parent_names[1] = si5351_input_names[1];
> +
> +       /* register PLLA */
> +       drvdata->pll[0].num = 0;
> +       drvdata->pll[0].drvdata = drvdata;
> +       drvdata->pll[0].hw.init = &init;
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       init.name = si5351_pll_names[0];
> +       init.ops = &si5351_pll_ops;
> +       init.flags = 0;
> +       init.parent_names = parent_names;
> +       init.num_parents = num_parents;
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register PLLB or VXCO (Si5351B) */
> +       drvdata->pll[1].num = 1;
> +       drvdata->pll[1].drvdata = drvdata;
> +       drvdata->pll[1].hw.init = &init;
> +       memset(&init, 0, sizeof(struct clk_init_data));
> +       if (drvdata->variant == SI5351_VARIANT_B) {
> +               init.name = si5351_pll_names[2];
> +               init.ops = &si5351_vxco_ops;
> +               init.flags = CLK_IS_ROOT;
> +               init.parent_names = NULL;
> +               init.num_parents = 0;
> +       } else {
> +               init.name = si5351_pll_names[1];
> +               init.ops = &si5351_pll_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +       }
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register clk multisync and clk out divider */
> +       num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +       parent_names[0] = si5351_pll_names[0];
> +       if (drvdata->variant == SI5351_VARIANT_B)
> +               parent_names[1] = si5351_pll_names[2];
> +       else
> +               parent_names[1] = si5351_pll_names[1];
> +
> +       drvdata->msynth = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +       drvdata->clkout = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> +       drvdata->onecell.clk_num = num_clocks;
> +       drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> +       if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +                   !drvdata->onecell.clks))
> +               return -ENOMEM;
> +
> +       for (n = 0; n < num_clocks; n++) {
> +               drvdata->msynth[n].num = n;
> +               drvdata->msynth[n].drvdata = drvdata;
> +               drvdata->msynth[n].hw.init = &init;
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_msynth_names[n];
> +               init.ops = &si5351_msynth_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = 2;
> +               clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +       parent_names[2] = si5351_input_names[0];
> +       parent_names[3] = si5351_input_names[1];
> +       for (n = 0; n < num_clocks; n++) {
> +               parent_names[0] = si5351_msynth_names[n];
> +               parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +                       si5351_msynth_names[4];
> +
> +               drvdata->clkout[n].num = n;
> +               drvdata->clkout[n].drvdata = drvdata;
> +               drvdata->clkout[n].hw.init = &init;
> +               memset(&init, 0, sizeof(struct clk_init_data));
> +               init.name = si5351_clkout_names[n];
> +               init.ops = &si5351_clkout_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +               drvdata->onecell.clks[n] = clk;
> +       }
> +
> +       /* setup clock setup from DT */
> +       si5351_dt_setup(client, drvdata);
> +
> +       of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +                           &drvdata->onecell);
> +
> +       dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> +       return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +       i2c_set_clientdata(client, NULL);
> +       return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +       { "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +       { "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +       .driver = {
> +               .name = "si5351",
> +               .of_match_table = si5351_dt_ids,
> +       },
> +       .probe = si5351_i2c_probe,
> +       .remove = si5351_i2c_remove,
> +       .id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +       return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +       i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR                   0x60
> +
> +#define SI5351_PLL_VCO_MIN                     600000000
> +#define SI5351_PLL_VCO_MAX                     900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ             1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ          150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ             160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ           SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ                 8000
> +#define SI5351_CLKOUT_MAX_FREQ                 SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ               SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN                       15
> +#define SI5351_PLL_A_MAX                       90
> +#define SI5351_PLL_B_MAX                       (SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX                       1048575
> +#define SI5351_MULTISYNTH_A_MIN                        6
> +#define SI5351_MULTISYNTH_A_MAX                        1800
> +#define SI5351_MULTISYNTH67_A_MAX              254
> +#define SI5351_MULTISYNTH_B_MAX                        (SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX                        1048575
> +#define SI5351_MULTISYNTH_P1_MAX               ((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX               ((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX               ((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS                   0
> +#define SI5351_INTERRUPT_STATUS                        1
> +#define SI5351_INTERRUPT_MASK                  2
> +#define  SI5351_STATUS_SYS_INIT                        (1<<7)
> +#define  SI5351_STATUS_LOL_B                   (1<<6)
> +#define  SI5351_STATUS_LOL_A                   (1<<5)
> +#define  SI5351_STATUS_LOS                     (1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL              3
> +#define SI5351_OEB_PIN_ENABLE_CTRL             9
> +#define SI5351_PLL_INPUT_SOURCE                        15
> +#define  SI5351_CLKIN_DIV_MASK                 (3<<6)
> +#define  SI5351_CLKIN_DIV_1                    (0<<6)
> +#define  SI5351_CLKIN_DIV_2                    (1<<6)
> +#define  SI5351_CLKIN_DIV_4                    (2<<6)
> +#define  SI5351_CLKIN_DIV_8                    (3<<6)
> +#define  SI5351_PLLB_SOURCE                    (1<<3)
> +#define  SI5351_PLLA_SOURCE                    (1<<2)
> +
> +#define SI5351_CLK0_CTRL                       16
> +#define SI5351_CLK1_CTRL                       17
> +#define SI5351_CLK2_CTRL                       18
> +#define SI5351_CLK3_CTRL                       19
> +#define SI5351_CLK4_CTRL                       20
> +#define SI5351_CLK5_CTRL                       21
> +#define SI5351_CLK6_CTRL                       22
> +#define SI5351_CLK7_CTRL                       23
> +#define  SI5351_CLK_POWERDOWN                  (1<<7)
> +#define  SI5351_CLK_INTEGER_MODE               (1<<6)
> +#define  SI5351_CLK_PLL_SELECT                 (1<<5)
> +#define  SI5351_CLK_INVERT                     (1<<4)
> +#define  SI5351_CLK_INPUT_MASK                 (3<<2)
> +#define  SI5351_CLK_INPUT_XTAL                 (0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN                        (1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4       (2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N         (3<<2)
> +#define  SI5351_CLK_DRIVE_MASK                 (3<<0)
> +#define  SI5351_CLK_DRIVE_2MA                  (0<<0)
> +#define  SI5351_CLK_DRIVE_4MA                  (1<<0)
> +#define  SI5351_CLK_DRIVE_6MA                  (2<<0)
> +#define  SI5351_CLK_DRIVE_8MA                  (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE            24
> +#define SI5351_CLK7_4_DISABLE_STATE            25
> +#define  SI5351_CLK_DISABLE_STATE_LOW          0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH         1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT                2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER                3
> +
> +#define SI5351_PARAMETERS_LENGTH               8
> +#define SI5351_PLLA_PARAMETERS                 26
> +#define SI5351_PLLB_PARAMETERS                 34
> +#define SI5351_CLK0_PARAMETERS                 42
> +#define SI5351_CLK1_PARAMETERS                 50
> +#define SI5351_CLK2_PARAMETERS                 58
> +#define SI5351_CLK3_PARAMETERS                 66
> +#define SI5351_CLK4_PARAMETERS                 74
> +#define SI5351_CLK5_PARAMETERS                 82
> +#define SI5351_CLK6_PARAMETERS                 90
> +#define SI5351_CLK7_PARAMETERS                 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER           92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK            (7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK           (7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT           4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT          0
> +#define  SI5351_OUTPUT_CLK_DIV_1               0
> +#define  SI5351_OUTPUT_CLK_DIV_2               1
> +#define  SI5351_OUTPUT_CLK_DIV_4               2
> +#define  SI5351_OUTPUT_CLK_DIV_8               3
> +#define  SI5351_OUTPUT_CLK_DIV_16              4
> +#define  SI5351_OUTPUT_CLK_DIV_32              5
> +#define  SI5351_OUTPUT_CLK_DIV_64              6
> +#define  SI5351_OUTPUT_CLK_DIV_128             7
> +#define  SI5351_OUTPUT_CLK_DIVBY4              (3<<2)
> +
> +#define SI5351_SSC_PARAM0                      149
> +#define SI5351_SSC_PARAM1                      150
> +#define SI5351_SSC_PARAM2                      151
> +#define SI5351_SSC_PARAM3                      152
> +#define SI5351_SSC_PARAM4                      153
> +#define SI5351_SSC_PARAM5                      154
> +#define SI5351_SSC_PARAM6                      155
> +#define SI5351_SSC_PARAM7                      156
> +#define SI5351_SSC_PARAM8                      157
> +#define SI5351_SSC_PARAM9                      158
> +#define SI5351_SSC_PARAM10                     159
> +#define SI5351_SSC_PARAM11                     160
> +#define SI5351_SSC_PARAM12                     161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW             162
> +#define SI5351_VXCO_PARAMETERS_MID             163
> +#define SI5351_VXCO_PARAMETERS_HIGH            164
> +
> +#define SI5351_CLK0_PHASE_OFFSET               165
> +#define SI5351_CLK1_PHASE_OFFSET               166
> +#define SI5351_CLK2_PHASE_OFFSET               167
> +#define SI5351_CLK3_PHASE_OFFSET               168
> +#define SI5351_CLK4_PHASE_OFFSET               169
> +#define SI5351_CLK5_PHASE_OFFSET               170
> +
> +#define SI5351_PLL_RESET                       177
> +#define  SI5351_PLL_RESET_B                    (1<<7)
> +#define  SI5351_PLL_RESET_A                    (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD                    183
> +#define  SI5351_CRYSTAL_LOAD_MASK              (3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF               (1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF               (2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF              (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE                   187
> +#define  SI5351_CLKIN_ENABLE                   (1<<7)
> +#define  SI5351_XTAL_ENABLE                    (1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE              (1<<4)
> +
> +#endif
> -- 
> 1.7.10.4

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] clk: add si5351 i2c common clock driver
  2013-03-20  0:26     ` Mike Turquette
@ 2013-03-20  8:20       ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-20  8:20 UTC (permalink / raw)
  To: Mike Turquette
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	Daniel Mack, Jean-Francois Moine, devicetree-discuss, linux-doc,
	linux-kernel, linux-arm-kernel

On 03/20/2013 01:26 AM, Mike Turquette wrote:
> Quoting Sebastian Hesselbarth (2013-03-18 03:43:17)
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> ---
>> Changes from v2:
>> - add curly brackets to if-else-statements (Reported by Daniel Mack)
>> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
>> - fix parameter address calculation for clk6/clk7
>>
>> Changes from v1:
>> - remove .is_enabled functions as they read from i2c
>>    (Reported by Daniel Mack)
>> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>>    its own multisync
>>
>
> I've just pushed out a new clk-next branch which includes Ulf Hansson's
> series to introduce the .is_prepared callback.  Perhaps you can use that
> where you were previously using .is_enabled?

Mike,

thanks for the info, but I think having .prepare_enable and regmap caching
is just fine for now. When .is_prepared is in mainline, I will re-add the
callbacks.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-20  8:20       ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-20  8:20 UTC (permalink / raw)
  To: linux-arm-kernel

On 03/20/2013 01:26 AM, Mike Turquette wrote:
> Quoting Sebastian Hesselbarth (2013-03-18 03:43:17)
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> ---
>> Changes from v2:
>> - add curly brackets to if-else-statements (Reported by Daniel Mack)
>> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
>> - fix parameter address calculation for clk6/clk7
>>
>> Changes from v1:
>> - remove .is_enabled functions as they read from i2c
>>    (Reported by Daniel Mack)
>> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>>    its own multisync
>>
>
> I've just pushed out a new clk-next branch which includes Ulf Hansson's
> series to introduce the .is_prepared callback.  Perhaps you can use that
> where you were previously using .is_enabled?

Mike,

thanks for the info, but I think having .prepare_enable and regmap caching
is just fine for now. When .is_prepared is in mainline, I will re-add the
callbacks.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] clk: add si5351 i2c common clock driver
  2013-03-18 10:43   ` Sebastian Hesselbarth
@ 2013-03-21 18:09     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 95+ messages in thread
From: Lars-Peter Clausen @ 2013-03-21 18:09 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On 03/18/2013 11:43 AM, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>

Hi,

Couple of comments inside.

> ---
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
[...]
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1709 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..f73d2d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- pll-master: boolean, multisynth can change pll frequency.

Custom properties need a vendor prefix.

[...]

> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..38540e7
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1429 @@
[...]
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");

Wouldn't it be better to not add the vxco clock if it is not supported?

> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
[..]
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);

MODULE_DEVICE_TABLE(of,  ....

> +static int si5351_i2c_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)

This should easily fit in one line.

> +{
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),

sizeof(*drvdata) is the preferred way of writing this, same for a few other
similar instances.

> +			       GFP_KERNEL);
> +	if (drvdata == NULL) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
[...]
> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +			    &drvdata->onecell);

You should check the return value of of_clk_add_provider

> +
> +	dev_info(&client->dev, "registered si5351 i2c client\n");
> +

That's just noise, imagine every driver would print such a line, your bootlog
would be scrolling for hours ;) I'd either remove it or make it dev_dbg

> +	return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +	i2c_set_clientdata(client, NULL);

This is not required anymore, the core takes care of it these days. I think you
can drop the whole remove callback.

> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +	{ }
> +};

This is not how it is supposed to be used. The second field is not the i2c
address of the device, but device specific data, which you can use inside your
probe function. Since your driver only supports dt based probe you can just set
it to 0.

> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);




> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = si5351_dt_ids,
> +	},
> +	.probe = si5351_i2c_probe,
> +	.remove = si5351_i2c_remove,
> +	.id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +	return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +	i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);

module_i2c_driver(si5351_driver) can be used to replace the two function above.

> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_MASK			(3<<0)
> +#define  SI5351_CLK_DRIVE_2MA			(0<<0)
> +#define  SI5351_CLK_DRIVE_4MA			(1<<0)
> +#define  SI5351_CLK_DRIVE_6MA			(2<<0)
> +#define  SI5351_CLK_DRIVE_8MA			(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-21 18:09     ` Lars-Peter Clausen
  0 siblings, 0 replies; 95+ messages in thread
From: Lars-Peter Clausen @ 2013-03-21 18:09 UTC (permalink / raw)
  To: linux-arm-kernel

On 03/18/2013 11:43 AM, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>

Hi,

Couple of comments inside.

> ---
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
[...]
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 +++
>  6 files changed, 1709 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..f73d2d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- pll-master: boolean, multisynth can change pll frequency.

Custom properties need a vendor prefix.

[...]

> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..38540e7
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1429 @@
[...]
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");

Wouldn't it be better to not add the vxco clock if it is not supported?

> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
[..]
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);

MODULE_DEVICE_TABLE(of,  ....

> +static int si5351_i2c_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)

This should easily fit in one line.

> +{
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),

sizeof(*drvdata) is the preferred way of writing this, same for a few other
similar instances.

> +			       GFP_KERNEL);
> +	if (drvdata == NULL) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
[...]
> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> +			    &drvdata->onecell);

You should check the return value of of_clk_add_provider

> +
> +	dev_info(&client->dev, "registered si5351 i2c client\n");
> +

That's just noise, imagine every driver would print such a line, your bootlog
would be scrolling for hours ;) I'd either remove it or make it dev_dbg

> +	return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> +	i2c_set_clientdata(client, NULL);

This is not required anymore, the core takes care of it these days. I think you
can drop the whole remove callback.

> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> +	{ }
> +};

This is not how it is supposed to be used. The second field is not the i2c
address of the device, but device specific data, which you can use inside your
probe function. Since your driver only supports dt based probe you can just set
it to 0.

> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);




> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = si5351_dt_ids,
> +	},
> +	.probe = si5351_i2c_probe,
> +	.remove = si5351_i2c_remove,
> +	.id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> +	return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> +	i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);

module_i2c_driver(si5351_driver) can be used to replace the two function above.

> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_MASK			(3<<0)
> +#define  SI5351_CLK_DRIVE_2MA			(0<<0)
> +#define  SI5351_CLK_DRIVE_4MA			(1<<0)
> +#define  SI5351_CLK_DRIVE_6MA			(2<<0)
> +#define  SI5351_CLK_DRIVE_8MA			(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] clk: add si5351 i2c common clock driver
  2013-03-21 18:09     ` Lars-Peter Clausen
@ 2013-03-21 21:32       ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-21 21:32 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On 03/21/2013 07:09 PM, Lars-Peter Clausen wrote:
> On 03/18/2013 11:43 AM, Sebastian Hesselbarth wrote:
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>
> Hi,
>
> Couple of comments inside.

Lars-Peter,

thanks for the review.

>> ---
> [...]
>> +==Child nodes==
>> +
>> +Each of the clock outputs can be overwritten individually by
>> +using a child node to the I2C device node. If a child node for a clock
>> +output is not set, the eeprom configuration is not overwritten.
>> +
>> +Required child node properties:
>> +- reg: number of clock output.
>> +
>> +Optional child node properties:
>> +- clock-source: source clock of the output divider stage N, shall be
>> +  0 = multisynth N
>> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
>> +  2 = xtal
>> +  3 = clkin (si5351c only)
>> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
>> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
>> +  divider.
>> +- pll-master: boolean, multisynth can change pll frequency.
>
> Custom properties need a vendor prefix.

Good catch, I will add "silabs" prefix here.

> [...]
>
>> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
>> new file mode 100644
>> index 0000000..38540e7
>> --- /dev/null
>> +++ b/drivers/clk/clk-si5351.c
>> @@ -0,0 +1,1429 @@
> [...]
>> +
>> +/*
>> + * Si5351 vxco clock input (Si5351B only)
>> + */
>> +
>> +static int si5351_vxco_prepare(struct clk_hw *hw)
>> +{
>> +	struct si5351_hw_data *hwdata =
>> +		container_of(hw, struct si5351_hw_data, hw);
>> +
>> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
>
> Wouldn't it be better to not add the vxco clock if it is not supported?

Hmm, I thought about that already and knew someone will suggest to remove it.
But, IMHO it is better to leave that functions there for two reasons:

1. It is only a small part of one of three supported si5351 variant and doesn't
take much space.

2. The most common user of this driver is a hardware engineer and I want him/her
to help to add support. No warning, no note.

I can make the clock registration fail, if that is what you suggest.

> [..]
>> +
>> +static const struct of_device_id si5351_dt_ids[] = {
>> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
>> +	{ .compatible = "silabs,si5351a-msop",
>> +					 .data = (void *)SI5351_VARIANT_A3, },
>> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
>> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
>
> MODULE_DEVICE_TABLE(of,  ....

Ack.

>> +static int si5351_i2c_probe(
>> +	struct i2c_client *client, const struct i2c_device_id *id)
>
> This should easily fit in one line.

Ack.

>> +{
>> +	struct si5351_driver_data *drvdata;
>> +	struct clk_init_data init;
>> +	struct clk *clk;
>> +	const char *parent_names[4];
>> +	u8 num_parents, num_clocks;
>> +	int ret, n;
>> +
>> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
>
> sizeof(*drvdata) is the preferred way of writing this, same for a few other
> similar instances.

Yeah, I will change that.

>> +			       GFP_KERNEL);
>> +	if (drvdata == NULL) {
>> +		dev_err(&client->dev, "unable to allocate driver data\n");
>> +		return -ENOMEM;
>> +	}
>> +
> [...]
>> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
>> +			&drvdata->onecell);
>
> You should check the return value of of_clk_add_provider

Ack.

>> +
>> +	dev_info(&client->dev, "registered si5351 i2c client\n");
>> +
>
> That's just noise, imagine every driver would print such a line, your bootlog
> would be scrolling for hours ;) I'd either remove it or make it dev_dbg

Actually, I understand not to have it, but if you don't want it you can still
boot with "quiet", can't you? Having one single line that helps you see it has
been probed helps a lot. But, I don't have a strong opinion on that and can
make it dev_dbg.

>> +	return 0;
>> +}
>> +
>> +static int si5351_i2c_remove(struct i2c_client *client)
>> +{
>> +	i2c_set_clientdata(client, NULL);
>
> This is not required anymore, the core takes care of it these days. I think you
> can drop the whole remove callback.

Ok.

>> +	return 0;
>> +}
>> +
>> +static const struct i2c_device_id si5351_i2c_ids[] = {
>> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
>> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
>> +	{ }
>> +};
>
> This is not how it is supposed to be used. The second field is not the i2c
> address of the device, but device specific data, which you can use inside your
> probe function. Since your driver only supports dt based probe you can just set
> it to 0.

Ok, thanks for the hint.

>> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
>> +
>> +static struct i2c_driver si5351_driver = {
>> +	.driver = {
>> +		.name = "si5351",
>> +		.of_match_table = si5351_dt_ids,
>> +	},
>> +	.probe = si5351_i2c_probe,
>> +	.remove = si5351_i2c_remove,
>> +	.id_table = si5351_i2c_ids,
>> +};
>> +
>> +static int __init si5351_module_init(void)
>> +{
>> +	return i2c_add_driver(&si5351_driver);
>> +}
>> +module_init(si5351_module_init);
>> +
>> +static void __exit si5351_module_exit(void)
>> +{
>> +	i2c_del_driver(&si5351_driver);
>> +}
>> +module_exit(si5351_module_exit);
>
> module_i2c_driver(si5351_driver) can be used to replace the two function above.

Ack.

>> +
>> +MODULE_AUTHOR("Sebastian Hesselbarth<sebastian.hesselbarth@gmail.de");
>> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
>> +MODULE_LICENSE("GPL");
[...]

Again, thanks for the review.

Sebastian


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-21 21:32       ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-21 21:32 UTC (permalink / raw)
  To: linux-arm-kernel

On 03/21/2013 07:09 PM, Lars-Peter Clausen wrote:
> On 03/18/2013 11:43 AM, Sebastian Hesselbarth wrote:
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>
> Hi,
>
> Couple of comments inside.

Lars-Peter,

thanks for the review.

>> ---
> [...]
>> +==Child nodes==
>> +
>> +Each of the clock outputs can be overwritten individually by
>> +using a child node to the I2C device node. If a child node for a clock
>> +output is not set, the eeprom configuration is not overwritten.
>> +
>> +Required child node properties:
>> +- reg: number of clock output.
>> +
>> +Optional child node properties:
>> +- clock-source: source clock of the output divider stage N, shall be
>> +  0 = multisynth N
>> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
>> +  2 = xtal
>> +  3 = clkin (si5351c only)
>> +- drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
>> +- multisynth-source: source pll A(0) or B(1) of corresponding multisynth
>> +  divider.
>> +- pll-master: boolean, multisynth can change pll frequency.
>
> Custom properties need a vendor prefix.

Good catch, I will add "silabs" prefix here.

> [...]
>
>> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
>> new file mode 100644
>> index 0000000..38540e7
>> --- /dev/null
>> +++ b/drivers/clk/clk-si5351.c
>> @@ -0,0 +1,1429 @@
> [...]
>> +
>> +/*
>> + * Si5351 vxco clock input (Si5351B only)
>> + */
>> +
>> +static int si5351_vxco_prepare(struct clk_hw *hw)
>> +{
>> +	struct si5351_hw_data *hwdata =
>> +		container_of(hw, struct si5351_hw_data, hw);
>> +
>> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
>
> Wouldn't it be better to not add the vxco clock if it is not supported?

Hmm, I thought about that already and knew someone will suggest to remove it.
But, IMHO it is better to leave that functions there for two reasons:

1. It is only a small part of one of three supported si5351 variant and doesn't
take much space.

2. The most common user of this driver is a hardware engineer and I want him/her
to help to add support. No warning, no note.

I can make the clock registration fail, if that is what you suggest.

> [..]
>> +
>> +static const struct of_device_id si5351_dt_ids[] = {
>> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
>> +	{ .compatible = "silabs,si5351a-msop",
>> +					 .data = (void *)SI5351_VARIANT_A3, },
>> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
>> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
>
> MODULE_DEVICE_TABLE(of,  ....

Ack.

>> +static int si5351_i2c_probe(
>> +	struct i2c_client *client, const struct i2c_device_id *id)
>
> This should easily fit in one line.

Ack.

>> +{
>> +	struct si5351_driver_data *drvdata;
>> +	struct clk_init_data init;
>> +	struct clk *clk;
>> +	const char *parent_names[4];
>> +	u8 num_parents, num_clocks;
>> +	int ret, n;
>> +
>> +	drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
>
> sizeof(*drvdata) is the preferred way of writing this, same for a few other
> similar instances.

Yeah, I will change that.

>> +			       GFP_KERNEL);
>> +	if (drvdata == NULL) {
>> +		dev_err(&client->dev, "unable to allocate driver data\n");
>> +		return -ENOMEM;
>> +	}
>> +
> [...]
>> +	of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
>> +			&drvdata->onecell);
>
> You should check the return value of of_clk_add_provider

Ack.

>> +
>> +	dev_info(&client->dev, "registered si5351 i2c client\n");
>> +
>
> That's just noise, imagine every driver would print such a line, your bootlog
> would be scrolling for hours ;) I'd either remove it or make it dev_dbg

Actually, I understand not to have it, but if you don't want it you can still
boot with "quiet", can't you? Having one single line that helps you see it has
been probed helps a lot. But, I don't have a strong opinion on that and can
make it dev_dbg.

>> +	return 0;
>> +}
>> +
>> +static int si5351_i2c_remove(struct i2c_client *client)
>> +{
>> +	i2c_set_clientdata(client, NULL);
>
> This is not required anymore, the core takes care of it these days. I think you
> can drop the whole remove callback.

Ok.

>> +	return 0;
>> +}
>> +
>> +static const struct i2c_device_id si5351_i2c_ids[] = {
>> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
>> +	{ "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
>> +	{ }
>> +};
>
> This is not how it is supposed to be used. The second field is not the i2c
> address of the device, but device specific data, which you can use inside your
> probe function. Since your driver only supports dt based probe you can just set
> it to 0.

Ok, thanks for the hint.

>> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
>> +
>> +static struct i2c_driver si5351_driver = {
>> +	.driver = {
>> +		.name = "si5351",
>> +		.of_match_table = si5351_dt_ids,
>> +	},
>> +	.probe = si5351_i2c_probe,
>> +	.remove = si5351_i2c_remove,
>> +	.id_table = si5351_i2c_ids,
>> +};
>> +
>> +static int __init si5351_module_init(void)
>> +{
>> +	return i2c_add_driver(&si5351_driver);
>> +}
>> +module_init(si5351_module_init);
>> +
>> +static void __exit si5351_module_exit(void)
>> +{
>> +	i2c_del_driver(&si5351_driver);
>> +}
>> +module_exit(si5351_module_exit);
>
> module_i2c_driver(si5351_driver) can be used to replace the two function above.

Ack.

>> +
>> +MODULE_AUTHOR("Sebastian Hesselbarth<sebastian.hesselbarth@gmail.de");
>> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
>> +MODULE_LICENSE("GPL");
[...]

Again, thanks for the review.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] clk: add si5351 i2c common clock driver
  2013-03-21 21:32       ` Sebastian Hesselbarth
@ 2013-03-23 10:07         ` Lars-Peter Clausen
  -1 siblings, 0 replies; 95+ messages in thread
From: Lars-Peter Clausen @ 2013-03-23 10:07 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On 03/21/2013 10:32 PM, Sebastian Hesselbarth wrote:
>>> +
>>> +    dev_info(&client->dev, "registered si5351 i2c client\n");
>>> +
>>
>> That's just noise, imagine every driver would print such a line, your bootlog
>> would be scrolling for hours ;) I'd either remove it or make it dev_dbg
> 
> Actually, I understand not to have it, but if you don't want it you can still
> boot with "quiet", can't you? Having one single line that helps you see it has
> been probed helps a lot. But, I don't have a strong opinion on that and can
> make it dev_dbg.

It is useful during development, but in my opinion only as long as not every
other driver does this as well. If you want to check whether a device has
been probed you can easily do this by checking the drivers sysfs node.

- Lars

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] clk: add si5351 i2c common clock driver
@ 2013-03-23 10:07         ` Lars-Peter Clausen
  0 siblings, 0 replies; 95+ messages in thread
From: Lars-Peter Clausen @ 2013-03-23 10:07 UTC (permalink / raw)
  To: linux-arm-kernel

On 03/21/2013 10:32 PM, Sebastian Hesselbarth wrote:
>>> +
>>> +    dev_info(&client->dev, "registered si5351 i2c client\n");
>>> +
>>
>> That's just noise, imagine every driver would print such a line, your bootlog
>> would be scrolling for hours ;) I'd either remove it or make it dev_dbg
> 
> Actually, I understand not to have it, but if you don't want it you can still
> boot with "quiet", can't you? Having one single line that helps you see it has
> been probed helps a lot. But, I don't have a strong opinion on that and can
> make it dev_dbg.

It is useful during development, but in my opinion only as long as not every
other driver does this as well. If you want to check whether a device has
been probed you can easily do this by checking the drivers sysfs node.

- Lars

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v4] clk: add si5351 i2c common clock driver
@ 2013-03-23 14:46     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-23 14:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	Lars-Peter Clausen, devicetree-discuss, linux-doc, linux-kernel,
	linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Tested-by: Daniel Mack <zonque@gmail.com>
---
Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |   10 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1411 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1692 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..6412f55 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,16 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	depends on OF
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..9d0c210
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1411 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline unsigned char si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "silabs,pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "silabs,pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "silabs,drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,multisynth-source",
+					  &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4] clk: add si5351 i2c common clock driver
@ 2013-03-23 14:46     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-23 14:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Jean-Francois Moine, Lars-Peter Clausen, Stephen Warren,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring,
	Russell King - ARM Linux, Dom Cobley, Mike Turquette,
	Andrew Morton, Rabeeh Khoury,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Tested-by: Daniel Mack <zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely-s3s/WqlpOiPyB63q8FvJNQ@public.gmane.org>
Cc: Rob Herring <rob.herring-bsGFqQB8/DxBDgjK7y7TUQ@public.gmane.org>
Cc: Rob Landley <rob-VoJi6FS/r0vR7s880joybQ@public.gmane.org>
Cc: Mike Turquette <mturquette-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Cc: Stephen Warren <swarren-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
Cc: Thierry Reding <thierry.reding-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>
Cc: Dom Cobley <popcornmix-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: Linus Walleij <linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Cc: Arnd Bergmann <arnd-r2nGTMty4D4@public.gmane.org>
Cc: Andrew Morton <akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b@public.gmane.org>
Cc: Russell King - ARM Linux <linux-lFZ/pmaqli7XmaaqVzeoHQ@public.gmane.org>
Cc: Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
Cc: Daniel Mack <zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
Cc: Lars-Peter Clausen <lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org>
Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org
Cc: linux-doc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |   10 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1411 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1692 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..6412f55 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,16 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	depends on OF
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..9d0c210
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1411 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+ * Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline unsigned char si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "silabs,pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "silabs,pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "silabs,drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,multisynth-source",
+					  &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth-EimvkypIwmU@public.gmane.org");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+ * Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _CLK_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4] clk: add si5351 i2c common clock driver
@ 2013-03-23 14:46     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-03-23 14:46 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Tested-by: Daniel Mack <zonque@gmail.com>
---
Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |   10 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1411 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1692 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..6412f55 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,16 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	depends on OF
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..9d0c210
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1411 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-private.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline unsigned char si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, *parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	if (is_master)
+		drvdata->msynth[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+	else
+		drvdata->msynth[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, a, b, c, divby4, *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name,
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	drvdata->clkout[num].hw.clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		drvdata->clkout[num].hw.clk->flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned val;
+
+	val = 0;
+	hw->clk->flags &= ~CLK_SET_RATE_PARENT;
+	switch (index) {
+	case 0:
+		hw->clk->flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
+		rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "silabs,pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "silabs,pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "silabs,drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,multisynth-source",
+					  &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		init.parent_names = &drvdata->pxtal->name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			init.parent_names = &drvdata->pclkin->name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v4] clk: add si5351 i2c common clock driver
  2013-03-23 14:46     ` Sebastian Hesselbarth
  (?)
@ 2013-04-02 23:46       ` Mike Turquette
  -1 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-02 23:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth, Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	Daniel Mack, Jean-Francois Moine, Lars-Peter Clausen,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-03-23 07:46:50)
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..9d0c210
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1411 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>

I hope that this is supposed to be clk-provider.h.  I don't plan on
taking in any more clock drivers that depend on clk-private.h.  I didn't
see any reason for this patch to not use clk-provider.h.

Regards,
Mike

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4] clk: add si5351 i2c common clock driver
@ 2013-04-02 23:46       ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-02 23:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	Daniel Mack, Jean-Francois Moine, Lars-Peter Clausen,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-03-23 07:46:50)
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..9d0c210
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1411 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>

I hope that this is supposed to be clk-provider.h.  I don't plan on
taking in any more clock drivers that depend on clk-private.h.  I didn't
see any reason for this patch to not use clk-provider.h.

Regards,
Mike

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v4] clk: add si5351 i2c common clock driver
@ 2013-04-02 23:46       ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-02 23:46 UTC (permalink / raw)
  To: linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-03-23 07:46:50)
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..9d0c210
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1411 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk-private.h>

I hope that this is supposed to be clk-provider.h.  I don't plan on
taking in any more clock drivers that depend on clk-private.h.  I didn't
see any reason for this patch to not use clk-provider.h.

Regards,
Mike

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4] clk: add si5351 i2c common clock driver
  2013-04-02 23:46       ` Mike Turquette
@ 2013-04-03 11:10         ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-03 11:10 UTC (permalink / raw)
  To: Mike Turquette
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Russell King - ARM Linux, Rabeeh Khoury,
	Daniel Mack, Jean-Francois Moine, Lars-Peter Clausen,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On Wed, Apr 3, 2013 at 1:46 AM, Mike Turquette <mturquette@linaro.org> wrote:
> Quoting Sebastian Hesselbarth (2013-03-23 07:46:50)
>> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
>> new file mode 100644
>> index 0000000..9d0c210
>> --- /dev/null
>> +++ b/drivers/clk/clk-si5351.c
>> @@ -0,0 +1,1411 @@
>> +/*
>> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
>> + *
>> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
>> + * Rabeeh Khoury <rabeeh@solid-run.com>
>> + *
>> + * References:
>> + * [1] "Si5351A/B/C Data Sheet"
>> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
>> + * [2] "Manually Generating an Si5351 Register Map"
>> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
>> + *
>> + * 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 <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/clk-private.h>
>
> I hope that this is supposed to be clk-provider.h.  I don't plan on
> taking in any more clock drivers that depend on clk-private.h.  I didn't
> see any reason for this patch to not use clk-provider.h.

True except for two things:
- The driver sets clk flags for the hierarchy after parsing the DT and
there is no helper __clk_set_flags()
- For single parent clks (pxtal, pclkin) I reuse &clk->name for .parent_names

For the latter I would have to add two static char *[1] somewhere, not
a big deal. But the for the flags,
I guess there will be no __clk_set_flags helper? Then I'd have to
parse the DT in advance to have the
correct flags ready at clk registration.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v4] clk: add si5351 i2c common clock driver
@ 2013-04-03 11:10         ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-03 11:10 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Apr 3, 2013 at 1:46 AM, Mike Turquette <mturquette@linaro.org> wrote:
> Quoting Sebastian Hesselbarth (2013-03-23 07:46:50)
>> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
>> new file mode 100644
>> index 0000000..9d0c210
>> --- /dev/null
>> +++ b/drivers/clk/clk-si5351.c
>> @@ -0,0 +1,1411 @@
>> +/*
>> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
>> + *
>> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
>> + * Rabeeh Khoury <rabeeh@solid-run.com>
>> + *
>> + * References:
>> + * [1] "Si5351A/B/C Data Sheet"
>> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
>> + * [2] "Manually Generating an Si5351 Register Map"
>> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
>> + *
>> + * 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 <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/clk-private.h>
>
> I hope that this is supposed to be clk-provider.h.  I don't plan on
> taking in any more clock drivers that depend on clk-private.h.  I didn't
> see any reason for this patch to not use clk-provider.h.

True except for two things:
- The driver sets clk flags for the hierarchy after parsing the DT and
there is no helper __clk_set_flags()
- For single parent clks (pxtal, pclkin) I reuse &clk->name for .parent_names

For the latter I would have to add two static char *[1] somewhere, not
a big deal. But the for the flags,
I guess there will be no __clk_set_flags helper? Then I'd have to
parse the DT in advance to have the
correct flags ready at clk registration.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v5] clk: add si5351 i2c common clock driver
  2013-03-23 14:46     ` Sebastian Hesselbarth
@ 2013-04-05  5:23       ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-05  5:23 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Tested-by: Daniel Mack <zonque@gmail.com>
---
Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |   10 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1710 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..6412f55 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,16 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	depends on OF
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..e259cec
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1429 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline unsigned char si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	unsigned long flags;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
+	if (is_master)
+		flags |= CLK_SET_RATE_PARENT;
+	else
+		flags &= ~CLK_SET_RATE_PARENT;
+	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+	unsigned long flags;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	flags = __clk_get_flags(drvdata->clkout[num].hw.clk);
+	flags &= ~CLK_SET_RATE_PARENT;
+
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	__clk_set_flags(drvdata->clkout[num].hw.clk, flags);
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long flags;
+	unsigned val = 0;
+
+	flags = __clk_get_flags(hw->clk) & ~CLK_SET_RATE_PARENT;
+
+	switch (index) {
+	case 0:
+		flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+	__clk_set_flags(hw->clk, flags);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "silabs,pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "silabs,pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "silabs,drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,multisynth-source",
+					  &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5] clk: add si5351 i2c common clock driver
@ 2013-04-05  5:23       ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-05  5:23 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver supports
DT kernels only and VXCO feature of si5351b is not implemented. DT
bindings selectively allow to overwrite stored Si5351 configuration
which is very helpful for clock generators with empty eeprom
configuration. Corresponding device tree binding documentation is
also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Tested-by: Daniel Mack <zonque@gmail.com>
---
Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |   10 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1429 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 6 files changed, 1710 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..6412f55 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,16 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	depends on OF
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..e259cec
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1429 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline unsigned char si5351_reg_read(struct si5351_driver_data *drvdata,
+	unsigned char reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg%02x\n", reg);
+		return 0;
+	}
+
+	return (unsigned char)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, unsigned char *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char count, const unsigned char *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+	unsigned char reg, unsigned char mask, unsigned char val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline unsigned char si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+	unsigned char reg, struct si5351_parameters *params)
+{
+	unsigned char buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				       unsigned char num, unsigned char parent)
+{
+	if (num > 2 ||
+	    (drvdata->variant == SI5351_VARIANT_B && num > 1))
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C && parent > 0)
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->pll[num].hw.clk, (parent) ?
+			      drvdata->clkin.clk : drvdata->xtal.clk);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char mask = (hwdata->num == 0) ?
+		SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE,
+			mask, (index) ? 0 : mask);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = (hwdata->num == 0) ?
+		SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS;
+	unsigned char val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1, fXTAL or fCLKIN/CLKIN_DIV
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_set_pll_master(
+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
+{
+	unsigned long flags;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return;
+
+	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
+	if (is_master)
+		flags |= CLK_SET_RATE_PARENT;
+	else
+		flags &= ~CLK_SET_RATE_PARENT;
+	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
+}
+
+static inline int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	if (parent > 2)
+		return -EINVAL;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	return clk_set_parent(drvdata->msynth[num].hw.clk,
+			      drvdata->pll[parent].hw.clk);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_PLL_SELECT,
+			(index) ? SI5351_CLK_PLL_SELECT : 0);
+
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_set_drive_strength(struct si5351_driver_data *drvdata,
+				     unsigned char num, unsigned char drive)
+{
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	switch (drive) {
+	case 2:
+		drive = SI5351_CLK_DRIVE_2MA;
+		break;
+	case 4:
+		drive = SI5351_CLK_DRIVE_4MA;
+		break;
+	case 6:
+		drive = SI5351_CLK_DRIVE_6MA;
+		break;
+	case 8:
+		drive = SI5351_CLK_DRIVE_8MA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_MASK, drive);
+
+	return 0;
+}
+
+static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  unsigned char num, unsigned char parent)
+{
+	struct clk *pclk;
+	unsigned long flags;
+
+	if (num > 8 ||
+	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
+		return -EINVAL;
+
+	flags = __clk_get_flags(drvdata->clkout[num].hw.clk);
+	flags &= ~CLK_SET_RATE_PARENT;
+
+	switch (parent) {
+	case 0:
+		pclk = drvdata->msynth[num].hw.clk;
+		flags |= CLK_SET_RATE_PARENT;
+		break;
+	case 1:
+		pclk = drvdata->msynth[0].hw.clk;
+		if (num >= 4)
+			pclk = drvdata->msynth[4].hw.clk;
+		break;
+	case 2:
+		pclk = drvdata->xtal.clk;
+		break;
+	case 3:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+		pclk = drvdata->clkin.clk;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	__clk_set_flags(drvdata->clkout[num].hw.clk, flags);
+	return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long flags;
+	unsigned val = 0;
+
+	flags = __clk_get_flags(hw->clk) & ~CLK_SET_RATE_PARENT;
+
+	switch (index) {
+	case 0:
+		flags |= CLK_SET_RATE_PARENT;
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case 1:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (hwdata->num == 0 || hwdata->num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case 2:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case 3:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	}
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INPUT_MASK, val);
+	__clk_set_flags(hw->clk, flags);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+static void si5351_dt_setup(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	struct property *prop;
+	const __be32 *p;
+	unsigned int num, val;
+
+	if (np == NULL)
+		return;
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(client->dev.of_node, "silabs,pll-source",
+				 prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		if (_si5351_pll_reparent(drvdata, num, val))
+			dev_warn(&client->dev,
+				 "unable to reparent pll %d to %d\n",
+				 num, val);
+	}
+
+	for_each_child_of_node(client->dev.of_node, np) {
+		if (of_property_read_u32(np, "reg", &num)) {
+			dev_err(&client->dev, "missing reg property of %s\n",
+				np->full_name);
+			continue;
+		}
+
+		if (of_property_read_bool(np, "silabs,pll-master"))
+			_si5351_msynth_set_pll_master(drvdata, num, 1);
+
+		if (!of_property_read_u32(np, "silabs,drive-strength", &val)) {
+			if (_si5351_clkout_set_drive_strength(drvdata,
+							      num, val))
+				dev_warn(&client->dev,
+					 "unable to set drive strength of %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,multisynth-source",
+					  &val)) {
+			if (_si5351_msynth_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent multisynth %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "silabs,clock-source", &val)) {
+			if (_si5351_clkout_reparent(drvdata, num, val))
+				dev_warn(&client->dev,
+					 "unable to reparent clockout %d to %d\n",
+					 num, val);
+		}
+
+		if (!of_property_read_u32(np, "clock-frequency", &val))
+			clk_set_rate(drvdata->onecell.clks[num], val);
+	}
+}
+
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(
+	struct i2c_client *client, struct si5351_driver_data *drvdata)
+{
+	struct device_node *np = client->dev.of_node;
+	const struct of_device_id *match;
+
+	if (np == NULL)
+		return -EINVAL;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	drvdata->variant = (enum si5351_variant)match->data;
+	drvdata->pxtal = of_clk_get(np, 0);
+	drvdata->pclkin = of_clk_get(np, 1);
+
+	return 0;
+}
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	ret = si5351_dt_parse(client, drvdata);
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	/* setup clock setup from DT */
+	si5351_dt_setup(client, drvdata);
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = si5351_dt_ids,
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.de");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..424073c
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_MASK			(3<<0)
+#define  SI5351_CLK_DRIVE_2MA			(0<<0)
+#define  SI5351_CLK_DRIVE_4MA			(1<<0)
+#define  SI5351_CLK_DRIVE_6MA			(2<<0)
+#define  SI5351_CLK_DRIVE_8MA			(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
  2013-04-05  5:23       ` Sebastian Hesselbarth
@ 2013-04-07 22:50         ` Guenter Roeck
  -1 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-07 22:50 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Tested-by: Daniel Mack <zonque@gmail.com>
>
[ ... ]

> +static inline void _si5351_msynth_set_pll_master(
> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> +{
> +	unsigned long flags;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return;
> +
> +	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> +	if (is_master)
> +		flags |= CLK_SET_RATE_PARENT;
> +	else
> +		flags &= ~CLK_SET_RATE_PARENT;
> +	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> +}
> +
Unless I am missing something, neither __clk_get_flags() nor the new
__clk_set_flags is exported.

Did you try to build and load the driver as module ?

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-07 22:50         ` Guenter Roeck
  0 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-07 22:50 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver supports
> DT kernels only and VXCO feature of si5351b is not implemented. DT
> bindings selectively allow to overwrite stored Si5351 configuration
> which is very helpful for clock generators with empty eeprom
> configuration. Corresponding device tree binding documentation is
> also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Tested-by: Daniel Mack <zonque@gmail.com>
>
[ ... ]

> +static inline void _si5351_msynth_set_pll_master(
> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> +{
> +	unsigned long flags;
> +
> +	if (num > 8 ||
> +	    (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> +		return;
> +
> +	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> +	if (is_master)
> +		flags |= CLK_SET_RATE_PARENT;
> +	else
> +		flags &= ~CLK_SET_RATE_PARENT;
> +	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> +}
> +
Unless I am missing something, neither __clk_get_flags() nor the new
__clk_set_flags is exported.

Did you try to build and load the driver as module ?

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
  2013-04-07 22:50         ` Guenter Roeck
@ 2013-04-07 23:49           ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-07 23:49 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> Tested-by: Daniel Mack<zonque@gmail.com>
>>
> [ ... ]
>
>> +static inline void _si5351_msynth_set_pll_master(
>> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>> +{
>> +	unsigned long flags;
>> +
>> +	if (num>  8 ||
>> +	    (drvdata->variant == SI5351_VARIANT_A3&&  num>  3))
>> +		return;
>> +
>> +	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>> +	if (is_master)
>> +		flags |= CLK_SET_RATE_PARENT;
>> +	else
>> +		flags&= ~CLK_SET_RATE_PARENT;
>> +	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>> +}
>> +
> Unless I am missing something, neither __clk_get_flags() nor the new
> __clk_set_flags is exported.
>
> Did you try to build and load the driver as module ?

Well, good catch. I didn't try to build v5 as a module, but I guess it
will fail. But I consider this as something that has to be addressed in
clk framework itself, not in this patch. There will be other
clk-providers built as module in the future for sure.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-07 23:49           ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-07 23:49 UTC (permalink / raw)
  To: linux-arm-kernel

On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver supports
>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>> bindings selectively allow to overwrite stored Si5351 configuration
>> which is very helpful for clock generators with empty eeprom
>> configuration. Corresponding device tree binding documentation is
>> also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> Tested-by: Daniel Mack<zonque@gmail.com>
>>
> [ ... ]
>
>> +static inline void _si5351_msynth_set_pll_master(
>> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>> +{
>> +	unsigned long flags;
>> +
>> +	if (num>  8 ||
>> +	    (drvdata->variant == SI5351_VARIANT_A3&&  num>  3))
>> +		return;
>> +
>> +	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>> +	if (is_master)
>> +		flags |= CLK_SET_RATE_PARENT;
>> +	else
>> +		flags&= ~CLK_SET_RATE_PARENT;
>> +	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>> +}
>> +
> Unless I am missing something, neither __clk_get_flags() nor the new
> __clk_set_flags is exported.
>
> Did you try to build and load the driver as module ?

Well, good catch. I didn't try to build v5 as a module, but I guess it
will fail. But I consider this as something that has to be addressed in
clk framework itself, not in this patch. There will be other
clk-providers built as module in the future for sure.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
  2013-04-07 23:49           ` Sebastian Hesselbarth
@ 2013-04-08  0:17             ` Guenter Roeck
  -1 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-08  0:17 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
> On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> >On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> >>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> >>i2c programmable clock generators. Currently, the driver supports
> >>DT kernels only and VXCO feature of si5351b is not implemented. DT
> >>bindings selectively allow to overwrite stored Si5351 configuration
> >>which is very helpful for clock generators with empty eeprom
> >>configuration. Corresponding device tree binding documentation is
> >>also added.
> >>
> >>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
> >>Tested-by: Daniel Mack<zonque@gmail.com>
> >>
> >[ ... ]
> >
> >>+static inline void _si5351_msynth_set_pll_master(
> >>+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> >>+{
> >>+	unsigned long flags;
> >>+
> >>+	if (num>  8 ||
> >>+	    (drvdata->variant == SI5351_VARIANT_A3&&  num>  3))
> >>+		return;
> >>+
> >>+	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> >>+	if (is_master)
> >>+		flags |= CLK_SET_RATE_PARENT;
> >>+	else
> >>+		flags&= ~CLK_SET_RATE_PARENT;
> >>+	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> >>+}
> >>+
> >Unless I am missing something, neither __clk_get_flags() nor the new
> >__clk_set_flags is exported.
> >
> >Did you try to build and load the driver as module ?
> 
> Well, good catch. I didn't try to build v5 as a module, but I guess it
> will fail. But I consider this as something that has to be addressed in
> clk framework itself, not in this patch. There will be other
> clk-providers built as module in the future for sure.
> 
Sure, but you provided the patch to make __clk_set_flags global. To avoid
build failures, I would suggest to either submit a patch to export the
missing functions, or to remove the ability to build the driver as module.

On a side note, do you happen to know anyone working on drivers for Si5319 or
Si5368 ?

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08  0:17             ` Guenter Roeck
  0 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-08  0:17 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
> On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> >On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> >>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> >>i2c programmable clock generators. Currently, the driver supports
> >>DT kernels only and VXCO feature of si5351b is not implemented. DT
> >>bindings selectively allow to overwrite stored Si5351 configuration
> >>which is very helpful for clock generators with empty eeprom
> >>configuration. Corresponding device tree binding documentation is
> >>also added.
> >>
> >>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
> >>Tested-by: Daniel Mack<zonque@gmail.com>
> >>
> >[ ... ]
> >
> >>+static inline void _si5351_msynth_set_pll_master(
> >>+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> >>+{
> >>+	unsigned long flags;
> >>+
> >>+	if (num>  8 ||
> >>+	    (drvdata->variant == SI5351_VARIANT_A3&&  num>  3))
> >>+		return;
> >>+
> >>+	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> >>+	if (is_master)
> >>+		flags |= CLK_SET_RATE_PARENT;
> >>+	else
> >>+		flags&= ~CLK_SET_RATE_PARENT;
> >>+	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> >>+}
> >>+
> >Unless I am missing something, neither __clk_get_flags() nor the new
> >__clk_set_flags is exported.
> >
> >Did you try to build and load the driver as module ?
> 
> Well, good catch. I didn't try to build v5 as a module, but I guess it
> will fail. But I consider this as something that has to be addressed in
> clk framework itself, not in this patch. There will be other
> clk-providers built as module in the future for sure.
> 
Sure, but you provided the patch to make __clk_set_flags global. To avoid
build failures, I would suggest to either submit a patch to export the
missing functions, or to remove the ability to build the driver as module.

On a side note, do you happen to know anyone working on drivers for Si5319 or
Si5368 ?

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
  2013-04-08  0:17             ` Guenter Roeck
@ 2013-04-08  6:11               ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08  6:11 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On 04/08/2013 02:17 AM, Guenter Roeck wrote:
> On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
>> On 04/08/2013 12:50 AM, Guenter Roeck wrote:
>>> On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>>>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>>>> i2c programmable clock generators. Currently, the driver supports
>>>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>>>> bindings selectively allow to overwrite stored Si5351 configuration
>>>> which is very helpful for clock generators with empty eeprom
>>>> configuration. Corresponding device tree binding documentation is
>>>> also added.
>>>>
>>>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>>>> Tested-by: Daniel Mack<zonque@gmail.com>
>>>>
>>> [ ... ]
>>>
>>>> +static inline void _si5351_msynth_set_pll_master(
>>>> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>>>> +{
>>>> +	unsigned long flags;
>>>> +
>>>> +	if (num>   8 ||
>>>> +	    (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
>>>> +		return;
>>>> +
>>>> +	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>>>> +	if (is_master)
>>>> +		flags |= CLK_SET_RATE_PARENT;
>>>> +	else
>>>> +		flags&= ~CLK_SET_RATE_PARENT;
>>>> +	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>>>> +}
>>>> +
>>> Unless I am missing something, neither __clk_get_flags() nor the new
>>> __clk_set_flags is exported.
>>>
>>> Did you try to build and load the driver as module ?
>>
>> Well, good catch. I didn't try to build v5 as a module, but I guess it
>> will fail. But I consider this as something that has to be addressed in
>> clk framework itself, not in this patch. There will be other
>> clk-providers built as module in the future for sure.
>>
> Sure, but you provided the patch to make __clk_set_flags global. To avoid
> build failures, I would suggest to either submit a patch to export the
> missing functions, or to remove the ability to build the driver as module.

Actually, I knew that __clk_set_flags patch will not be accepted before 
posting it ;)

> On a side note, do you happen to know anyone working on drivers for Si5319 or
> Si5368 ?

No.

> Thanks,
> Guenter


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08  6:11               ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08  6:11 UTC (permalink / raw)
  To: linux-arm-kernel

On 04/08/2013 02:17 AM, Guenter Roeck wrote:
> On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
>> On 04/08/2013 12:50 AM, Guenter Roeck wrote:
>>> On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>>>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>>>> i2c programmable clock generators. Currently, the driver supports
>>>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>>>> bindings selectively allow to overwrite stored Si5351 configuration
>>>> which is very helpful for clock generators with empty eeprom
>>>> configuration. Corresponding device tree binding documentation is
>>>> also added.
>>>>
>>>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>>>> Tested-by: Daniel Mack<zonque@gmail.com>
>>>>
>>> [ ... ]
>>>
>>>> +static inline void _si5351_msynth_set_pll_master(
>>>> +	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>>>> +{
>>>> +	unsigned long flags;
>>>> +
>>>> +	if (num>   8 ||
>>>> +	    (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
>>>> +		return;
>>>> +
>>>> +	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>>>> +	if (is_master)
>>>> +		flags |= CLK_SET_RATE_PARENT;
>>>> +	else
>>>> +		flags&= ~CLK_SET_RATE_PARENT;
>>>> +	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>>>> +}
>>>> +
>>> Unless I am missing something, neither __clk_get_flags() nor the new
>>> __clk_set_flags is exported.
>>>
>>> Did you try to build and load the driver as module ?
>>
>> Well, good catch. I didn't try to build v5 as a module, but I guess it
>> will fail. But I consider this as something that has to be addressed in
>> clk framework itself, not in this patch. There will be other
>> clk-providers built as module in the future for sure.
>>
> Sure, but you provided the patch to make __clk_set_flags global. To avoid
> build failures, I would suggest to either submit a patch to export the
> missing functions, or to remove the ability to build the driver as module.

Actually, I knew that __clk_set_flags patch will not be accepted before 
posting it ;)

> On a side note, do you happen to know anyone working on drivers for Si5319 or
> Si5368 ?

No.

> Thanks,
> Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 14:54                 ` Guenter Roeck
  0 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-08 14:54 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> >>>>i2c programmable clock generators. Currently, the driver supports
> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
> >>>>bindings selectively allow to overwrite stored Si5351 configuration
> >>>>which is very helpful for clock generators with empty eeprom
> >>>>configuration. Corresponding device tree binding documentation is
> >>>>also added.
> >>>>
> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
> >>>>Tested-by: Daniel Mack<zonque@gmail.com>
> >>>>
> >>>[ ... ]
> >>>
> >>>>+static inline void _si5351_msynth_set_pll_master(
> >>>>+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> >>>>+{
> >>>>+	unsigned long flags;
> >>>>+
> >>>>+	if (num>   8 ||
> >>>>+	    (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
> >>>>+		return;
> >>>>+
> >>>>+	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> >>>>+	if (is_master)
> >>>>+		flags |= CLK_SET_RATE_PARENT;
> >>>>+	else
> >>>>+		flags&= ~CLK_SET_RATE_PARENT;
> >>>>+	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> >>>>+}
> >>>>+
> >>>Unless I am missing something, neither __clk_get_flags() nor the new
> >>>__clk_set_flags is exported.
> >>>
> >>>Did you try to build and load the driver as module ?
> >>
> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
> >>will fail. But I consider this as something that has to be addressed in
> >>clk framework itself, not in this patch. There will be other
> >>clk-providers built as module in the future for sure.
> >>
> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
> >build failures, I would suggest to either submit a patch to export the
> >missing functions, or to remove the ability to build the driver as module.
> 
> Actually, I knew that __clk_set_flags patch will not be accepted
> before posting it ;)
> 
Ah, but part of that is to get you to think about it again, and to defend it if
it is really needed. After all, "it can be abused" applies to pretty much every
API.

Key question is if you _really_ need run-time flag modifications, or if you can
live with initialization-time settings. If you really need it, you'll have to
explain the reasons.

> >On a side note, do you happen to know anyone working on drivers for Si5319 or
> >Si5368 ?
> 
> No.

Too bad ... I may have to write that code myself then.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 14:54                 ` Guenter Roeck
  0 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-08 14:54 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Jean-Francois Moine, Mark Brown, Stephen Warren, Pawel Moll,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring,
	Russell King - ARM Linux, Dom Cobley, Mike Turquette,
	Andrew Morton, Rabeeh Khoury, Lars-Peter Clausen,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> >>>>i2c programmable clock generators. Currently, the driver supports
> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
> >>>>bindings selectively allow to overwrite stored Si5351 configuration
> >>>>which is very helpful for clock generators with empty eeprom
> >>>>configuration. Corresponding device tree binding documentation is
> >>>>also added.
> >>>>
> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> >>>>Tested-by: Daniel Mack<zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> >>>>
> >>>[ ... ]
> >>>
> >>>>+static inline void _si5351_msynth_set_pll_master(
> >>>>+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> >>>>+{
> >>>>+	unsigned long flags;
> >>>>+
> >>>>+	if (num>   8 ||
> >>>>+	    (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
> >>>>+		return;
> >>>>+
> >>>>+	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> >>>>+	if (is_master)
> >>>>+		flags |= CLK_SET_RATE_PARENT;
> >>>>+	else
> >>>>+		flags&= ~CLK_SET_RATE_PARENT;
> >>>>+	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> >>>>+}
> >>>>+
> >>>Unless I am missing something, neither __clk_get_flags() nor the new
> >>>__clk_set_flags is exported.
> >>>
> >>>Did you try to build and load the driver as module ?
> >>
> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
> >>will fail. But I consider this as something that has to be addressed in
> >>clk framework itself, not in this patch. There will be other
> >>clk-providers built as module in the future for sure.
> >>
> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
> >build failures, I would suggest to either submit a patch to export the
> >missing functions, or to remove the ability to build the driver as module.
> 
> Actually, I knew that __clk_set_flags patch will not be accepted
> before posting it ;)
> 
Ah, but part of that is to get you to think about it again, and to defend it if
it is really needed. After all, "it can be abused" applies to pretty much every
API.

Key question is if you _really_ need run-time flag modifications, or if you can
live with initialization-time settings. If you really need it, you'll have to
explain the reasons.

> >On a side note, do you happen to know anyone working on drivers for Si5319 or
> >Si5368 ?
> 
> No.

Too bad ... I may have to write that code myself then.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 14:54                 ` Guenter Roeck
  0 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-08 14:54 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> >>>>i2c programmable clock generators. Currently, the driver supports
> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
> >>>>bindings selectively allow to overwrite stored Si5351 configuration
> >>>>which is very helpful for clock generators with empty eeprom
> >>>>configuration. Corresponding device tree binding documentation is
> >>>>also added.
> >>>>
> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
> >>>>Tested-by: Daniel Mack<zonque@gmail.com>
> >>>>
> >>>[ ... ]
> >>>
> >>>>+static inline void _si5351_msynth_set_pll_master(
> >>>>+	struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> >>>>+{
> >>>>+	unsigned long flags;
> >>>>+
> >>>>+	if (num>   8 ||
> >>>>+	    (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
> >>>>+		return;
> >>>>+
> >>>>+	flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> >>>>+	if (is_master)
> >>>>+		flags |= CLK_SET_RATE_PARENT;
> >>>>+	else
> >>>>+		flags&= ~CLK_SET_RATE_PARENT;
> >>>>+	__clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> >>>>+}
> >>>>+
> >>>Unless I am missing something, neither __clk_get_flags() nor the new
> >>>__clk_set_flags is exported.
> >>>
> >>>Did you try to build and load the driver as module ?
> >>
> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
> >>will fail. But I consider this as something that has to be addressed in
> >>clk framework itself, not in this patch. There will be other
> >>clk-providers built as module in the future for sure.
> >>
> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
> >build failures, I would suggest to either submit a patch to export the
> >missing functions, or to remove the ability to build the driver as module.
> 
> Actually, I knew that __clk_set_flags patch will not be accepted
> before posting it ;)
> 
Ah, but part of that is to get you to think about it again, and to defend it if
it is really needed. After all, "it can be abused" applies to pretty much every
API.

Key question is if you _really_ need run-time flag modifications, or if you can
live with initialization-time settings. If you really need it, you'll have to
explain the reasons.

> >On a side note, do you happen to know anyone working on drivers for Si5319 or
> >Si5368 ?
> 
> No.

Too bad ... I may have to write that code myself then.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 15:38                   ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 15:38 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
>> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
>> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
>> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
>> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> >>>>i2c programmable clock generators. Currently, the driver supports
>> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
>> >>>>bindings selectively allow to overwrite stored Si5351 configuration
>> >>>>which is very helpful for clock generators with empty eeprom
>> >>>>configuration. Corresponding device tree binding documentation is
>> >>>>also added.
>> >>>>
>> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> >>>>Tested-by: Daniel Mack<zonque@gmail.com>
>> >>>>
>> >>>[ ... ]
>> >>>
>> >>>>+static inline void _si5351_msynth_set_pll_master(
>> >>>>+ struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>> >>>>+{
>> >>>>+ unsigned long flags;
>> >>>>+
>> >>>>+ if (num>   8 ||
>> >>>>+     (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
>> >>>>+         return;
>> >>>>+
>> >>>>+ flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>> >>>>+ if (is_master)
>> >>>>+         flags |= CLK_SET_RATE_PARENT;
>> >>>>+ else
>> >>>>+         flags&= ~CLK_SET_RATE_PARENT;
>> >>>>+ __clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>> >>>>+}
>> >>>>+
>> >>>Unless I am missing something, neither __clk_get_flags() nor the new
>> >>>__clk_set_flags is exported.
>> >>>
>> >>>Did you try to build and load the driver as module ?
>> >>
>> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
>> >>will fail. But I consider this as something that has to be addressed in
>> >>clk framework itself, not in this patch. There will be other
>> >>clk-providers built as module in the future for sure.
>> >>
>> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
>> >build failures, I would suggest to either submit a patch to export the
>> >missing functions, or to remove the ability to build the driver as module.
>>
>> Actually, I knew that __clk_set_flags patch will not be accepted
>> before posting it ;)
>>
> Ah, but part of that is to get you to think about it again, and to defend it if
> it is really needed. After all, "it can be abused" applies to pretty much every
> API.

Guenter,

I already thought about it a lot and I consider clk api broken in a way here.

> Key question is if you _really_ need run-time flag modifications, or if you can
> live with initialization-time settings. If you really need it, you'll have to
> explain the reasons.

The question is not if _I_ really need run-time flags but why the api allows to
perform run-time modifications of the clock hierarchy without allowing different
flags? There is clk_set_parent() so I guess clk api knows about run-time changes
already, but you cannot have different flags per parent. And with
__clk_set_flags()
rejected, you are not allowed to change the flags.

I understand that some flags are permanent and required at registration, but
CLK_SET_PARENT_RATE is not. It is not limited by hardware but limited by api.
One way would be a more generic clk-mux with per parent flags, but for
the current
implementation, I cannot see how clk-mux can be exploited here.

I can live with it, because then dynamic muxing of clock hierarchy
within clk-si5351
is just not supported or will break function. Currently, there is no
support for dynamic
muxing, so everything is fine.

>> >On a side note, do you happen to know anyone working on drivers for Si5319 or
>> >Si5368 ?
>>
>> No.
>
> Too bad ... I may have to write that code myself then.

Well, if clk-si5351 will ever get in mainline kernel, feel free to use
it as a template ;)

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 15:38                   ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 15:38 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Jean-Francois Moine, Mark Brown, Stephen Warren, Pawel Moll,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring,
	Russell King - ARM Linux, Dom Cobley, Mike Turquette,
	Andrew Morton, Rabeeh Khoury, Lars-Peter Clausen,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck <linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org> wrote:
> On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
>> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
>> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
>> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
>> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> >>>>i2c programmable clock generators. Currently, the driver supports
>> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
>> >>>>bindings selectively allow to overwrite stored Si5351 configuration
>> >>>>which is very helpful for clock generators with empty eeprom
>> >>>>configuration. Corresponding device tree binding documentation is
>> >>>>also added.
>> >>>>
>> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> >>>>Tested-by: Daniel Mack<zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> >>>>
>> >>>[ ... ]
>> >>>
>> >>>>+static inline void _si5351_msynth_set_pll_master(
>> >>>>+ struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>> >>>>+{
>> >>>>+ unsigned long flags;
>> >>>>+
>> >>>>+ if (num>   8 ||
>> >>>>+     (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
>> >>>>+         return;
>> >>>>+
>> >>>>+ flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>> >>>>+ if (is_master)
>> >>>>+         flags |= CLK_SET_RATE_PARENT;
>> >>>>+ else
>> >>>>+         flags&= ~CLK_SET_RATE_PARENT;
>> >>>>+ __clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>> >>>>+}
>> >>>>+
>> >>>Unless I am missing something, neither __clk_get_flags() nor the new
>> >>>__clk_set_flags is exported.
>> >>>
>> >>>Did you try to build and load the driver as module ?
>> >>
>> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
>> >>will fail. But I consider this as something that has to be addressed in
>> >>clk framework itself, not in this patch. There will be other
>> >>clk-providers built as module in the future for sure.
>> >>
>> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
>> >build failures, I would suggest to either submit a patch to export the
>> >missing functions, or to remove the ability to build the driver as module.
>>
>> Actually, I knew that __clk_set_flags patch will not be accepted
>> before posting it ;)
>>
> Ah, but part of that is to get you to think about it again, and to defend it if
> it is really needed. After all, "it can be abused" applies to pretty much every
> API.

Guenter,

I already thought about it a lot and I consider clk api broken in a way here.

> Key question is if you _really_ need run-time flag modifications, or if you can
> live with initialization-time settings. If you really need it, you'll have to
> explain the reasons.

The question is not if _I_ really need run-time flags but why the api allows to
perform run-time modifications of the clock hierarchy without allowing different
flags? There is clk_set_parent() so I guess clk api knows about run-time changes
already, but you cannot have different flags per parent. And with
__clk_set_flags()
rejected, you are not allowed to change the flags.

I understand that some flags are permanent and required at registration, but
CLK_SET_PARENT_RATE is not. It is not limited by hardware but limited by api.
One way would be a more generic clk-mux with per parent flags, but for
the current
implementation, I cannot see how clk-mux can be exploited here.

I can live with it, because then dynamic muxing of clock hierarchy
within clk-si5351
is just not supported or will break function. Currently, there is no
support for dynamic
muxing, so everything is fine.

>> >On a side note, do you happen to know anyone working on drivers for Si5319 or
>> >Si5368 ?
>>
>> No.
>
> Too bad ... I may have to write that code myself then.

Well, if clk-si5351 will ever get in mainline kernel, feel free to use
it as a template ;)

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 15:38                   ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 15:38 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
>> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
>> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
>> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
>> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> >>>>i2c programmable clock generators. Currently, the driver supports
>> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
>> >>>>bindings selectively allow to overwrite stored Si5351 configuration
>> >>>>which is very helpful for clock generators with empty eeprom
>> >>>>configuration. Corresponding device tree binding documentation is
>> >>>>also added.
>> >>>>
>> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>> >>>>Tested-by: Daniel Mack<zonque@gmail.com>
>> >>>>
>> >>>[ ... ]
>> >>>
>> >>>>+static inline void _si5351_msynth_set_pll_master(
>> >>>>+ struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>> >>>>+{
>> >>>>+ unsigned long flags;
>> >>>>+
>> >>>>+ if (num>   8 ||
>> >>>>+     (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
>> >>>>+         return;
>> >>>>+
>> >>>>+ flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>> >>>>+ if (is_master)
>> >>>>+         flags |= CLK_SET_RATE_PARENT;
>> >>>>+ else
>> >>>>+         flags&= ~CLK_SET_RATE_PARENT;
>> >>>>+ __clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>> >>>>+}
>> >>>>+
>> >>>Unless I am missing something, neither __clk_get_flags() nor the new
>> >>>__clk_set_flags is exported.
>> >>>
>> >>>Did you try to build and load the driver as module ?
>> >>
>> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
>> >>will fail. But I consider this as something that has to be addressed in
>> >>clk framework itself, not in this patch. There will be other
>> >>clk-providers built as module in the future for sure.
>> >>
>> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
>> >build failures, I would suggest to either submit a patch to export the
>> >missing functions, or to remove the ability to build the driver as module.
>>
>> Actually, I knew that __clk_set_flags patch will not be accepted
>> before posting it ;)
>>
> Ah, but part of that is to get you to think about it again, and to defend it if
> it is really needed. After all, "it can be abused" applies to pretty much every
> API.

Guenter,

I already thought about it a lot and I consider clk api broken in a way here.

> Key question is if you _really_ need run-time flag modifications, or if you can
> live with initialization-time settings. If you really need it, you'll have to
> explain the reasons.

The question is not if _I_ really need run-time flags but why the api allows to
perform run-time modifications of the clock hierarchy without allowing different
flags? There is clk_set_parent() so I guess clk api knows about run-time changes
already, but you cannot have different flags per parent. And with
__clk_set_flags()
rejected, you are not allowed to change the flags.

I understand that some flags are permanent and required at registration, but
CLK_SET_PARENT_RATE is not. It is not limited by hardware but limited by api.
One way would be a more generic clk-mux with per parent flags, but for
the current
implementation, I cannot see how clk-mux can be exploited here.

I can live with it, because then dynamic muxing of clock hierarchy
within clk-si5351
is just not supported or will break function. Currently, there is no
support for dynamic
muxing, so everything is fine.

>> >On a side note, do you happen to know anyone working on drivers for Si5319 or
>> >Si5368 ?
>>
>> No.
>
> Too bad ... I may have to write that code myself then.

Well, if clk-si5351 will ever get in mainline kernel, feel free to use
it as a template ;)

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v6] clk: add si5351 i2c common clock driver
  2013-04-05  5:23       ` Sebastian Hesselbarth
@ 2013-04-08 16:46         ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 16:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allow to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
This v5 introduces a rather big change compared to v4, so I removed
the Daniel Mack's Tested-by. The driver now parses the device tree
prior clock registration to have all clk flags ready. The plus of this
approach is that, the driver now can be used on non-DT kernels as well.

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1462 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1856 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..f849601
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1462 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline void _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+					int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	_si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	u8 val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	_si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+				(index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+				SI5351_MULTISYNTH_SRC_VCO1);
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static inline void _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+}
+
+static inline void _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	_si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_warn(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			continue;
+		}
+	}
+
+	/* per clkout properties */
+	num = of_get_child_count(np);
+
+	if (num == 0)
+		return 0;
+
+	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);
+			continue;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_warn(&client->dev,
+					 "invalid parent %d for multisynth %d\n",
+					 val, num);
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_warn(&client->dev,
+					 "invalid parent %d for clkout %d\n",
+					 val, num);
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_warn(&client->dev,
+					 "invalid drive strength %d for clkout %d\n",
+					 val, num);
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++)
+		_si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+
+	for (n = 0; n < 8; n++) {
+		_si5351_msynth_reparent(drvdata, n,
+					pdata->clkout[n].multisynth_src);
+		_si5351_clkout_reparent(drvdata, n,
+					pdata->clkout[n].clkout_src);
+		_si5351_clkout_set_drive_strength(drvdata, n,
+					pdata->clkout[n].drive);
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..f62218e
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6] clk: add si5351 i2c common clock driver
@ 2013-04-08 16:46         ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 16:46 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allow to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
This v5 introduces a rather big change compared to v4, so I removed
the Daniel Mack's Tested-by. The driver now parses the device tree
prior clock registration to have all clk flags ready. The plus of this
approach is that, the driver now can be used on non-DT kernels as well.

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1462 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 +++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1856 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..f849601
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1462 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static inline void _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+					int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	_si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+
+	return 0;
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	u8 val;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	/* reset pll */
+	val = (hwdata->num == 0) ? SI5351_PLL_RESET_A : SI5351_PLL_RESET_B;
+	si5351_set_bits(hwdata->drvdata, SI5351_PLL_RESET, val, val);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static inline void _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	_si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+				(index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+				SI5351_MULTISYNTH_SRC_VCO1);
+	return 0;
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static inline void _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				  int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+}
+
+static inline void _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	_si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+
+	return 0;
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* powerdown clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			break;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p)
+			break;
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_warn(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			continue;
+		}
+	}
+
+	/* per clkout properties */
+	num = of_get_child_count(np);
+
+	if (num == 0)
+		return 0;
+
+	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);
+			continue;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_warn(&client->dev,
+					 "invalid parent %d for multisynth %d\n",
+					 val, num);
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_warn(&client->dev,
+					 "invalid parent %d for clkout %d\n",
+					 val, num);
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_warn(&client->dev,
+					 "invalid drive strength %d for clkout %d\n",
+					 val, num);
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++)
+		_si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+
+	for (n = 0; n < 8; n++) {
+		_si5351_msynth_reparent(drvdata, n,
+					pdata->clkout[n].multisynth_src);
+		_si5351_clkout_reparent(drvdata, n,
+					pdata->clkout[n].clkout_src);
+		_si5351_clkout_set_drive_strength(drvdata, n,
+					pdata->clkout[n].drive);
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..f62218e
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
  2013-04-08 15:38                   ` Sebastian Hesselbarth
@ 2013-04-08 17:36                     ` Mike Turquette
  -1 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-08 17:36 UTC (permalink / raw)
  To: Sebastian Hesselbarth, Guenter Roeck
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Pawel Moll, Mark Brown, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	Lars-Peter Clausen, devicetree-discuss, linux-doc, linux-kernel,
	linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-04-08 08:38:44)
> On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
> >> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
> >> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
> >> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> >> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> >> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> >> >>>>i2c programmable clock generators. Currently, the driver supports
> >> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
> >> >>>>bindings selectively allow to overwrite stored Si5351 configuration
> >> >>>>which is very helpful for clock generators with empty eeprom
> >> >>>>configuration. Corresponding device tree binding documentation is
> >> >>>>also added.
> >> >>>>
> >> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
> >> >>>>Tested-by: Daniel Mack<zonque@gmail.com>
> >> >>>>
> >> >>>[ ... ]
> >> >>>
> >> >>>>+static inline void _si5351_msynth_set_pll_master(
> >> >>>>+ struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> >> >>>>+{
> >> >>>>+ unsigned long flags;
> >> >>>>+
> >> >>>>+ if (num>   8 ||
> >> >>>>+     (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
> >> >>>>+         return;
> >> >>>>+
> >> >>>>+ flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> >> >>>>+ if (is_master)
> >> >>>>+         flags |= CLK_SET_RATE_PARENT;
> >> >>>>+ else
> >> >>>>+         flags&= ~CLK_SET_RATE_PARENT;
> >> >>>>+ __clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> >> >>>>+}
> >> >>>>+
> >> >>>Unless I am missing something, neither __clk_get_flags() nor the new
> >> >>>__clk_set_flags is exported.
> >> >>>
> >> >>>Did you try to build and load the driver as module ?
> >> >>
> >> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
> >> >>will fail. But I consider this as something that has to be addressed in
> >> >>clk framework itself, not in this patch. There will be other
> >> >>clk-providers built as module in the future for sure.
> >> >>
> >> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
> >> >build failures, I would suggest to either submit a patch to export the
> >> >missing functions, or to remove the ability to build the driver as module.
> >>
> >> Actually, I knew that __clk_set_flags patch will not be accepted
> >> before posting it ;)
> >>
> > Ah, but part of that is to get you to think about it again, and to defend it if
> > it is really needed. After all, "it can be abused" applies to pretty much every
> > API.
> 
> Guenter,
> 
> I already thought about it a lot and I consider clk api broken in a way here.
> 
> > Key question is if you _really_ need run-time flag modifications, or if you can
> > live with initialization-time settings. If you really need it, you'll have to
> > explain the reasons.
> 
> The question is not if _I_ really need run-time flags but why the api allows to
> perform run-time modifications of the clock hierarchy without allowing different
> flags? There is clk_set_parent() so I guess clk api knows about run-time changes
> already, but you cannot have different flags per parent. And with
> __clk_set_flags()
> rejected, you are not allowed to change the flags.
> 
> I understand that some flags are permanent and required at registration, but
> CLK_SET_PARENT_RATE is not. It is not limited by hardware but limited by api.
> One way would be a more generic clk-mux with per parent flags, but for
> the current
> implementation, I cannot see how clk-mux can be exploited here.
> 

There are a couple of ways to get past this issue.  One is removal of
some of the flags.  I have never liked CLK_SET_RATE_PARENT, since I
think the ability to propagate a rate-change request up to the parent
should be enabled for all clocks.  This is in the interest of the driver
author who does not care to know intimate details of the clock
hierarchy.

When I developed the rate-change propagation code I was likely too hasty
in defining a per-clock flag for that behavior.  It might have been
enough to simply compare the &best_parent_rate value from .round_rate
and compare it against clk->parent->rate.  This means that no flag is
necessary.

This assumes that the .round_rate implementation has enough knowledge to
know whether or not to propagate the rate-change request up to the
parent.  This may not not true for the common divider type.

I'll make some tests on removing this flag (and potentially other flags)
to see how painful it is.

Regards,
Mike

> I can live with it, because then dynamic muxing of clock hierarchy
> within clk-si5351
> is just not supported or will break function. Currently, there is no
> support for dynamic
> muxing, so everything is fine.
> 
> >> >On a side note, do you happen to know anyone working on drivers for Si5319 or
> >> >Si5368 ?
> >>
> >> No.
> >
> > Too bad ... I may have to write that code myself then.
> 
> Well, if clk-si5351 will ever get in mainline kernel, feel free to use
> it as a template ;)
> 
> Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 17:36                     ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-08 17:36 UTC (permalink / raw)
  To: linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-04-08 08:38:44)
> On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
> >> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
> >> >On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
> >> >>On 04/08/2013 12:50 AM, Guenter Roeck wrote:
> >> >>>On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
> >> >>>>This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> >> >>>>i2c programmable clock generators. Currently, the driver supports
> >> >>>>DT kernels only and VXCO feature of si5351b is not implemented. DT
> >> >>>>bindings selectively allow to overwrite stored Si5351 configuration
> >> >>>>which is very helpful for clock generators with empty eeprom
> >> >>>>configuration. Corresponding device tree binding documentation is
> >> >>>>also added.
> >> >>>>
> >> >>>>Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
> >> >>>>Tested-by: Daniel Mack<zonque@gmail.com>
> >> >>>>
> >> >>>[ ... ]
> >> >>>
> >> >>>>+static inline void _si5351_msynth_set_pll_master(
> >> >>>>+ struct si5351_driver_data *drvdata, unsigned char num, int is_master)
> >> >>>>+{
> >> >>>>+ unsigned long flags;
> >> >>>>+
> >> >>>>+ if (num>   8 ||
> >> >>>>+     (drvdata->variant == SI5351_VARIANT_A3&&   num>   3))
> >> >>>>+         return;
> >> >>>>+
> >> >>>>+ flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
> >> >>>>+ if (is_master)
> >> >>>>+         flags |= CLK_SET_RATE_PARENT;
> >> >>>>+ else
> >> >>>>+         flags&= ~CLK_SET_RATE_PARENT;
> >> >>>>+ __clk_set_flags(drvdata->msynth[num].hw.clk, flags);
> >> >>>>+}
> >> >>>>+
> >> >>>Unless I am missing something, neither __clk_get_flags() nor the new
> >> >>>__clk_set_flags is exported.
> >> >>>
> >> >>>Did you try to build and load the driver as module ?
> >> >>
> >> >>Well, good catch. I didn't try to build v5 as a module, but I guess it
> >> >>will fail. But I consider this as something that has to be addressed in
> >> >>clk framework itself, not in this patch. There will be other
> >> >>clk-providers built as module in the future for sure.
> >> >>
> >> >Sure, but you provided the patch to make __clk_set_flags global. To avoid
> >> >build failures, I would suggest to either submit a patch to export the
> >> >missing functions, or to remove the ability to build the driver as module.
> >>
> >> Actually, I knew that __clk_set_flags patch will not be accepted
> >> before posting it ;)
> >>
> > Ah, but part of that is to get you to think about it again, and to defend it if
> > it is really needed. After all, "it can be abused" applies to pretty much every
> > API.
> 
> Guenter,
> 
> I already thought about it a lot and I consider clk api broken in a way here.
> 
> > Key question is if you _really_ need run-time flag modifications, or if you can
> > live with initialization-time settings. If you really need it, you'll have to
> > explain the reasons.
> 
> The question is not if _I_ really need run-time flags but why the api allows to
> perform run-time modifications of the clock hierarchy without allowing different
> flags? There is clk_set_parent() so I guess clk api knows about run-time changes
> already, but you cannot have different flags per parent. And with
> __clk_set_flags()
> rejected, you are not allowed to change the flags.
> 
> I understand that some flags are permanent and required at registration, but
> CLK_SET_PARENT_RATE is not. It is not limited by hardware but limited by api.
> One way would be a more generic clk-mux with per parent flags, but for
> the current
> implementation, I cannot see how clk-mux can be exploited here.
> 

There are a couple of ways to get past this issue.  One is removal of
some of the flags.  I have never liked CLK_SET_RATE_PARENT, since I
think the ability to propagate a rate-change request up to the parent
should be enabled for all clocks.  This is in the interest of the driver
author who does not care to know intimate details of the clock
hierarchy.

When I developed the rate-change propagation code I was likely too hasty
in defining a per-clock flag for that behavior.  It might have been
enough to simply compare the &best_parent_rate value from .round_rate
and compare it against clk->parent->rate.  This means that no flag is
necessary.

This assumes that the .round_rate implementation has enough knowledge to
know whether or not to propagate the rate-change request up to the
parent.  This may not not true for the common divider type.

I'll make some tests on removing this flag (and potentially other flags)
to see how painful it is.

Regards,
Mike

> I can live with it, because then dynamic muxing of clock hierarchy
> within clk-si5351
> is just not supported or will break function. Currently, there is no
> support for dynamic
> muxing, so everything is fine.
> 
> >> >On a side note, do you happen to know anyone working on drivers for Si5319 or
> >> >Si5368 ?
> >>
> >> No.
> >
> > Too bad ... I may have to write that code myself then.
> 
> Well, if clk-si5351 will ever get in mainline kernel, feel free to use
> it as a template ;)
> 
> Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v6] clk: add si5351 i2c common clock driver
  2013-04-08 16:46         ` Sebastian Hesselbarth
@ 2013-04-08 17:46           ` Guenter Roeck
  -1 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-08 17:46 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On Mon, Apr 08, 2013 at 06:46:57PM +0200, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allow to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>

[ ... ]

> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			break;

You report dev_err here, but you don't return an error to the caller.
Is this on purpose ? If it is not a fatal error, maybe it should be dev_info ?

> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p)
> +			break;
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_warn(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			continue;

Same here, and a couple of times below. Given the context, I think it would
be better if the error cases would return an error. After all, the condition
suggests that the device tree data is wrong, meaning one has to assume it
won't work anyway and should be fixed in the device tree and not be ignored
by the driver.

> +		}
> +	}
> +
> +	/* per clkout properties */
> +	num = of_get_child_count(np);
> +
> +	if (num == 0)
> +		return 0;
> +

This doesn't set client->dev.platform_data yet returns no error, meaning the
calling code will either use provided platform data or fail after all if it is
NULL. That seems to be inconsistent, given that a dt entry was already detected.
The user might end up wondering why the provided device tree data is not used,
not realizing that it is incomplete.

If children are not mandatory, you could just drop the code above and go through
for_each_child_of_node() anyway; it won't do anything and set
client->dev.platform_data at the end. If children are mandatory, it might make
sense to return an eror here (if there is dt information, it should be complete
and consistent).

> +	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);
> +			continue;
> +		}
> +
What if "num" returns 100 ? Or 1000 ?

> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_warn(&client->dev,
> +					 "invalid parent %d for multisynth %d\n",
> +					 val, num);
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_warn(&client->dev,
> +					 "invalid parent %d for clkout %d\n",
> +					 val, num);
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_warn(&client->dev,
> +					 "invalid drive strength %d for clkout %d\n",
> +					 val, num);
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++)
> +		_si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +
> +	for (n = 0; n < 8; n++) {
> +		_si5351_msynth_reparent(drvdata, n,
> +					pdata->clkout[n].multisynth_src);
> +		_si5351_clkout_reparent(drvdata, n,
> +					pdata->clkout[n].clkout_src);
> +		_si5351_clkout_set_drive_strength(drvdata, n,
> +					pdata->clkout[n].drive);
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..f62218e
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> -- 
> 1.7.10.4
> 
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v6] clk: add si5351 i2c common clock driver
@ 2013-04-08 17:46           ` Guenter Roeck
  0 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-08 17:46 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Apr 08, 2013 at 06:46:57PM +0200, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allow to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>

[ ... ]

> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			break;

You report dev_err here, but you don't return an error to the caller.
Is this on purpose ? If it is not a fatal error, maybe it should be dev_info ?

> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p)
> +			break;
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_warn(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			continue;

Same here, and a couple of times below. Given the context, I think it would
be better if the error cases would return an error. After all, the condition
suggests that the device tree data is wrong, meaning one has to assume it
won't work anyway and should be fixed in the device tree and not be ignored
by the driver.

> +		}
> +	}
> +
> +	/* per clkout properties */
> +	num = of_get_child_count(np);
> +
> +	if (num == 0)
> +		return 0;
> +

This doesn't set client->dev.platform_data yet returns no error, meaning the
calling code will either use provided platform data or fail after all if it is
NULL. That seems to be inconsistent, given that a dt entry was already detected.
The user might end up wondering why the provided device tree data is not used,
not realizing that it is incomplete.

If children are not mandatory, you could just drop the code above and go through
for_each_child_of_node() anyway; it won't do anything and set
client->dev.platform_data at the end. If children are mandatory, it might make
sense to return an eror here (if there is dt information, it should be complete
and consistent).

> +	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);
> +			continue;
> +		}
> +
What if "num" returns 100 ? Or 1000 ?

> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_warn(&client->dev,
> +					 "invalid parent %d for multisynth %d\n",
> +					 val, num);
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_warn(&client->dev,
> +					 "invalid parent %d for clkout %d\n",
> +					 val, num);
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_warn(&client->dev,
> +					 "invalid drive strength %d for clkout %d\n",
> +					 val, num);
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++)
> +		_si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +
> +	for (n = 0; n < 8; n++) {
> +		_si5351_msynth_reparent(drvdata, n,
> +					pdata->clkout[n].multisynth_src);
> +		_si5351_clkout_reparent(drvdata, n,
> +					pdata->clkout[n].clkout_src);
> +		_si5351_clkout_set_drive_strength(drvdata, n,
> +					pdata->clkout[n].drive);
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..f62218e
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> -- 
> 1.7.10.4
> 
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v6] clk: add si5351 i2c common clock driver
  2013-04-08 17:46           ` Guenter Roeck
@ 2013-04-08 18:24             ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 18:24 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On 04/08/2013 07:46 PM, Guenter Roeck wrote:
> On Mon, Apr 08, 2013 at 06:46:57PM +0200, Sebastian Hesselbarth wrote:
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver does not
>> support VXCO feature of si5351b. Passing platform_data or DT bindings
>> selectively allow to overwrite stored Si5351 configuration which is
>> very helpful for clock generators with empty eeprom configuration.
>> Corresponding device tree binding documentation is also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>
> [ ... ]
>
>> +
>> +/*
>> + * Si5351 i2c probe and DT
>> + */
>> +#ifdef CONFIG_OF
>> +static const struct of_device_id si5351_dt_ids[] = {
>> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
>> +	{ .compatible = "silabs,si5351a-msop",
>> +					 .data = (void *)SI5351_VARIANT_A3, },
>> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
>> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
>> +
>> +static int si5351_dt_parse(struct i2c_client *client)
>> +{
>> +	struct device_node *child, *np = client->dev.of_node;
>> +	struct si5351_platform_data *pdata;
>> +	const struct of_device_id *match;
>> +	struct property *prop;
>> +	const __be32 *p;
>> +	int num = 0;
>> +	u32 val;
>> +
>> +	if (np == NULL)
>> +		return 0;
>> +
>> +	match = of_match_node(si5351_dt_ids, np);
>> +	if (match == NULL)
>> +		return -EINVAL;
>> +
>> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
>> +	if (!pdata)
>> +		return -ENOMEM;
>> +
>> +	pdata->variant = (enum si5351_variant)match->data;
>> +	pdata->clk_xtal = of_clk_get(np, 0);
>> +	if (!IS_ERR(pdata->clk_xtal))
>> +		clk_put(pdata->clk_xtal);
>> +	pdata->clk_clkin = of_clk_get(np, 1);
>> +	if (!IS_ERR(pdata->clk_clkin))
>> +		clk_put(pdata->clk_clkin);
>> +
>> +	/*
>> +	 * property silabs,pll-source :<num src>, [<..>]
>> +	 * allow to selectively set pll source
>> +	 */
>> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
>> +		if (num>= 2) {
>> +			dev_err(&client->dev,
>> +				"invalid pll %d on pll-source prop\n", num);
>> +			break;
>
> You report dev_err here, but you don't return an error to the caller.
> Is this on purpose ? If it is not a fatal error, maybe it should be dev_info ?

This shouldn't break but continue with one u32 skipped. Actually, it is
a warning because somebody passed an invalid value and should be 
dev_warn(). But see my note below, I will make them all fatal.

>> +		}
>> +
>> +		p = of_prop_next_u32(prop, p,&val);
>> +		if (!p)
>> +			break;
>> +
>> +		switch (val) {
>> +		case 0:
>> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
>> +			break;
>> +		case 1:
>> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
>> +			break;
>> +		default:
>> +			dev_warn(&client->dev,
>> +				 "invalid parent %d for pll %d\n", val, num);
>> +			continue;
>
> Same here, and a couple of times below. Given the context, I think it would
> be better if the error cases would return an error. After all, the condition
> suggests that the device tree data is wrong, meaning one has to assume it
> won't work anyway and should be fixed in the device tree and not be ignored
> by the driver.

I am skipping invalid DT data enties here, but I can make them all
fatal. I will also add more variant checks in the corresponding
_si5351_* functions.

>> +		}
>> +	}
>> +
>> +	/* per clkout properties */
>> +	num = of_get_child_count(np);
>> +
>> +	if (num == 0)
>> +		return 0;
>> +
>
> This doesn't set client->dev.platform_data yet returns no error, meaning the
> calling code will either use provided platform data or fail after all if it is
> NULL. That seems to be inconsistent, given that a dt entry was already detected.
> The user might end up wondering why the provided device tree data is not used,
> not realizing that it is incomplete.
>
> If children are not mandatory, you could just drop the code above and go through
> for_each_child_of_node() anyway; it won't do anything and set
> client->dev.platform_data at the end. If children are mandatory, it might make
> sense to return an eror here (if there is dt information, it should be complete
> and consistent).

Not having children is valid as you only provide them if you want to
overwrite the current config stored in the eeprom. But yes, skipping
for_each_child_of_node below is a left-over from an implementation where
I used dynamically allocated ->pll/->clkout.

>> +	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);
>> +			continue;
>> +		}
>> +
> What if "num" returns 100 ? Or 1000 ?

Segmentation fault? ;) I will add a check for 0 <= num < 8.

Thanks for the review,
   Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v6] clk: add si5351 i2c common clock driver
@ 2013-04-08 18:24             ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 18:24 UTC (permalink / raw)
  To: linux-arm-kernel

On 04/08/2013 07:46 PM, Guenter Roeck wrote:
> On Mon, Apr 08, 2013 at 06:46:57PM +0200, Sebastian Hesselbarth wrote:
>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>> i2c programmable clock generators. Currently, the driver does not
>> support VXCO feature of si5351b. Passing platform_data or DT bindings
>> selectively allow to overwrite stored Si5351 configuration which is
>> very helpful for clock generators with empty eeprom configuration.
>> Corresponding device tree binding documentation is also added.
>>
>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>
> [ ... ]
>
>> +
>> +/*
>> + * Si5351 i2c probe and DT
>> + */
>> +#ifdef CONFIG_OF
>> +static const struct of_device_id si5351_dt_ids[] = {
>> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
>> +	{ .compatible = "silabs,si5351a-msop",
>> +					 .data = (void *)SI5351_VARIANT_A3, },
>> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
>> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
>> +
>> +static int si5351_dt_parse(struct i2c_client *client)
>> +{
>> +	struct device_node *child, *np = client->dev.of_node;
>> +	struct si5351_platform_data *pdata;
>> +	const struct of_device_id *match;
>> +	struct property *prop;
>> +	const __be32 *p;
>> +	int num = 0;
>> +	u32 val;
>> +
>> +	if (np == NULL)
>> +		return 0;
>> +
>> +	match = of_match_node(si5351_dt_ids, np);
>> +	if (match == NULL)
>> +		return -EINVAL;
>> +
>> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
>> +	if (!pdata)
>> +		return -ENOMEM;
>> +
>> +	pdata->variant = (enum si5351_variant)match->data;
>> +	pdata->clk_xtal = of_clk_get(np, 0);
>> +	if (!IS_ERR(pdata->clk_xtal))
>> +		clk_put(pdata->clk_xtal);
>> +	pdata->clk_clkin = of_clk_get(np, 1);
>> +	if (!IS_ERR(pdata->clk_clkin))
>> +		clk_put(pdata->clk_clkin);
>> +
>> +	/*
>> +	 * property silabs,pll-source :<num src>, [<..>]
>> +	 * allow to selectively set pll source
>> +	 */
>> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
>> +		if (num>= 2) {
>> +			dev_err(&client->dev,
>> +				"invalid pll %d on pll-source prop\n", num);
>> +			break;
>
> You report dev_err here, but you don't return an error to the caller.
> Is this on purpose ? If it is not a fatal error, maybe it should be dev_info ?

This shouldn't break but continue with one u32 skipped. Actually, it is
a warning because somebody passed an invalid value and should be 
dev_warn(). But see my note below, I will make them all fatal.

>> +		}
>> +
>> +		p = of_prop_next_u32(prop, p,&val);
>> +		if (!p)
>> +			break;
>> +
>> +		switch (val) {
>> +		case 0:
>> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
>> +			break;
>> +		case 1:
>> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
>> +			break;
>> +		default:
>> +			dev_warn(&client->dev,
>> +				 "invalid parent %d for pll %d\n", val, num);
>> +			continue;
>
> Same here, and a couple of times below. Given the context, I think it would
> be better if the error cases would return an error. After all, the condition
> suggests that the device tree data is wrong, meaning one has to assume it
> won't work anyway and should be fixed in the device tree and not be ignored
> by the driver.

I am skipping invalid DT data enties here, but I can make them all
fatal. I will also add more variant checks in the corresponding
_si5351_* functions.

>> +		}
>> +	}
>> +
>> +	/* per clkout properties */
>> +	num = of_get_child_count(np);
>> +
>> +	if (num == 0)
>> +		return 0;
>> +
>
> This doesn't set client->dev.platform_data yet returns no error, meaning the
> calling code will either use provided platform data or fail after all if it is
> NULL. That seems to be inconsistent, given that a dt entry was already detected.
> The user might end up wondering why the provided device tree data is not used,
> not realizing that it is incomplete.
>
> If children are not mandatory, you could just drop the code above and go through
> for_each_child_of_node() anyway; it won't do anything and set
> client->dev.platform_data at the end. If children are mandatory, it might make
> sense to return an eror here (if there is dt information, it should be complete
> and consistent).

Not having children is valid as you only provide them if you want to
overwrite the current config stored in the eeprom. But yes, skipping
for_each_child_of_node below is a left-over from an implementation where
I used dynamically allocated ->pll/->clkout.

>> +	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);
>> +			continue;
>> +		}
>> +
> What if "num" returns 100 ? Or 1000 ?

Segmentation fault? ;) I will add a check for 0 <= num < 8.

Thanks for the review,
   Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [v5] clk: add si5351 i2c common clock driver
  2013-04-08 17:36                     ` Mike Turquette
@ 2013-04-08 18:32                       ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 18:32 UTC (permalink / raw)
  To: Mike Turquette
  Cc: Guenter Roeck, Grant Likely, Rob Herring, Rob Landley,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, devicetree-discuss,
	linux-doc, linux-kernel, linux-arm-kernel

On 04/08/2013 07:36 PM, Mike Turquette wrote:
> Quoting Sebastian Hesselbarth (2013-04-08 08:38:44)
>> On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck<linux@roeck-us.net>  wrote:
>>> On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
>>>> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
>>>>> On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
>>>>>> On 04/08/2013 12:50 AM, Guenter Roeck wrote:
>>>>>>> On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>>>>>>>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>>>>>>>> i2c programmable clock generators. Currently, the driver supports
>>>>>>>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>>>>>>>> bindings selectively allow to overwrite stored Si5351 configuration
>>>>>>>> which is very helpful for clock generators with empty eeprom
>>>>>>>> configuration. Corresponding device tree binding documentation is
>>>>>>>> also added.
>>>>>>>>
>>>>>>>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>>>>>>>> Tested-by: Daniel Mack<zonque@gmail.com>
>>>>>>>>
>>>>>>> [ ... ]
>>>>>>>
>>>>>>>> +static inline void _si5351_msynth_set_pll_master(
>>>>>>>> + struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>>>>>>>> +{
>>>>>>>> + unsigned long flags;
>>>>>>>> +
>>>>>>>> + if (num>    8 ||
>>>>>>>> +     (drvdata->variant == SI5351_VARIANT_A3&&    num>    3))
>>>>>>>> +         return;
>>>>>>>> +
>>>>>>>> + flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>>>>>>>> + if (is_master)
>>>>>>>> +         flags |= CLK_SET_RATE_PARENT;
>>>>>>>> + else
>>>>>>>> +         flags&= ~CLK_SET_RATE_PARENT;
>>>>>>>> + __clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>>>>>>>> +}
>>>>>>>> +
>>>>>>> Unless I am missing something, neither __clk_get_flags() nor the new
>>>>>>> __clk_set_flags is exported.
>>>>>>>
>>>>>>> Did you try to build and load the driver as module ?
>>>>>>
>>>>>> Well, good catch. I didn't try to build v5 as a module, but I guess it
>>>>>> will fail. But I consider this as something that has to be addressed in
>>>>>> clk framework itself, not in this patch. There will be other
>>>>>> clk-providers built as module in the future for sure.
>>>>>>
>>>>> Sure, but you provided the patch to make __clk_set_flags global. To avoid
>>>>> build failures, I would suggest to either submit a patch to export the
>>>>> missing functions, or to remove the ability to build the driver as module.
>>>>
>>>> Actually, I knew that __clk_set_flags patch will not be accepted
>>>> before posting it ;)
>>>>
>>> Ah, but part of that is to get you to think about it again, and to defend it if
>>> it is really needed. After all, "it can be abused" applies to pretty much every
>>> API.
>>
>> Guenter,
>>
>> I already thought about it a lot and I consider clk api broken in a way here.
>>
>>> Key question is if you _really_ need run-time flag modifications, or if you can
>>> live with initialization-time settings. If you really need it, you'll have to
>>> explain the reasons.
>>
>> The question is not if _I_ really need run-time flags but why the api allows to
>> perform run-time modifications of the clock hierarchy without allowing different
>> flags? There is clk_set_parent() so I guess clk api knows about run-time changes
>> already, but you cannot have different flags per parent. And with
>> __clk_set_flags()
>> rejected, you are not allowed to change the flags.
>>
>> I understand that some flags are permanent and required at registration, but
>> CLK_SET_PARENT_RATE is not. It is not limited by hardware but limited by api.
>> One way would be a more generic clk-mux with per parent flags, but for
>> the current
>> implementation, I cannot see how clk-mux can be exploited here.
>>
>
> There are a couple of ways to get past this issue.  One is removal of
> some of the flags.  I have never liked CLK_SET_RATE_PARENT, since I
> think the ability to propagate a rate-change request up to the parent
> should be enabled for all clocks.  This is in the interest of the driver
> author who does not care to know intimate details of the clock
> hierarchy.
>
> When I developed the rate-change propagation code I was likely too hasty
> in defining a per-clock flag for that behavior.  It might have been
> enough to simply compare the&best_parent_rate value from .round_rate
> and compare it against clk->parent->rate.  This means that no flag is
> necessary.
>
> This assumes that the .round_rate implementation has enough knowledge to
> know whether or not to propagate the rate-change request up to the
> parent.  This may not not true for the common divider type.
>
> I'll make some tests on removing this flag (and potentially other flags)
> to see how painful it is.

Mike,

another good thing would be to have implementation independent building 
blocks for clk drivers. I think clk-fixed-rate, clk-gate, clk-mux and
some clk-synth should be enough. The current core clk templates in the
api are almost all special implementations.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [v5] clk: add si5351 i2c common clock driver
@ 2013-04-08 18:32                       ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-08 18:32 UTC (permalink / raw)
  To: linux-arm-kernel

On 04/08/2013 07:36 PM, Mike Turquette wrote:
> Quoting Sebastian Hesselbarth (2013-04-08 08:38:44)
>> On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck<linux@roeck-us.net>  wrote:
>>> On Mon, Apr 08, 2013 at 08:11:38AM +0200, Sebastian Hesselbarth wrote:
>>>> On 04/08/2013 02:17 AM, Guenter Roeck wrote:
>>>>> On Mon, Apr 08, 2013 at 01:49:24AM +0200, Sebastian Hesselbarth wrote:
>>>>>> On 04/08/2013 12:50 AM, Guenter Roeck wrote:
>>>>>>> On Fri, Apr 05, 2013 at 05:23:35AM -0000, Sebastian Hesselbarth wrote:
>>>>>>>> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
>>>>>>>> i2c programmable clock generators. Currently, the driver supports
>>>>>>>> DT kernels only and VXCO feature of si5351b is not implemented. DT
>>>>>>>> bindings selectively allow to overwrite stored Si5351 configuration
>>>>>>>> which is very helpful for clock generators with empty eeprom
>>>>>>>> configuration. Corresponding device tree binding documentation is
>>>>>>>> also added.
>>>>>>>>
>>>>>>>> Signed-off-by: Sebastian Hesselbarth<sebastian.hesselbarth@gmail.com>
>>>>>>>> Tested-by: Daniel Mack<zonque@gmail.com>
>>>>>>>>
>>>>>>> [ ... ]
>>>>>>>
>>>>>>>> +static inline void _si5351_msynth_set_pll_master(
>>>>>>>> + struct si5351_driver_data *drvdata, unsigned char num, int is_master)
>>>>>>>> +{
>>>>>>>> + unsigned long flags;
>>>>>>>> +
>>>>>>>> + if (num>    8 ||
>>>>>>>> +     (drvdata->variant == SI5351_VARIANT_A3&&    num>    3))
>>>>>>>> +         return;
>>>>>>>> +
>>>>>>>> + flags = __clk_get_flags(drvdata->msynth[num].hw.clk);
>>>>>>>> + if (is_master)
>>>>>>>> +         flags |= CLK_SET_RATE_PARENT;
>>>>>>>> + else
>>>>>>>> +         flags&= ~CLK_SET_RATE_PARENT;
>>>>>>>> + __clk_set_flags(drvdata->msynth[num].hw.clk, flags);
>>>>>>>> +}
>>>>>>>> +
>>>>>>> Unless I am missing something, neither __clk_get_flags() nor the new
>>>>>>> __clk_set_flags is exported.
>>>>>>>
>>>>>>> Did you try to build and load the driver as module ?
>>>>>>
>>>>>> Well, good catch. I didn't try to build v5 as a module, but I guess it
>>>>>> will fail. But I consider this as something that has to be addressed in
>>>>>> clk framework itself, not in this patch. There will be other
>>>>>> clk-providers built as module in the future for sure.
>>>>>>
>>>>> Sure, but you provided the patch to make __clk_set_flags global. To avoid
>>>>> build failures, I would suggest to either submit a patch to export the
>>>>> missing functions, or to remove the ability to build the driver as module.
>>>>
>>>> Actually, I knew that __clk_set_flags patch will not be accepted
>>>> before posting it ;)
>>>>
>>> Ah, but part of that is to get you to think about it again, and to defend it if
>>> it is really needed. After all, "it can be abused" applies to pretty much every
>>> API.
>>
>> Guenter,
>>
>> I already thought about it a lot and I consider clk api broken in a way here.
>>
>>> Key question is if you _really_ need run-time flag modifications, or if you can
>>> live with initialization-time settings. If you really need it, you'll have to
>>> explain the reasons.
>>
>> The question is not if _I_ really need run-time flags but why the api allows to
>> perform run-time modifications of the clock hierarchy without allowing different
>> flags? There is clk_set_parent() so I guess clk api knows about run-time changes
>> already, but you cannot have different flags per parent. And with
>> __clk_set_flags()
>> rejected, you are not allowed to change the flags.
>>
>> I understand that some flags are permanent and required at registration, but
>> CLK_SET_PARENT_RATE is not. It is not limited by hardware but limited by api.
>> One way would be a more generic clk-mux with per parent flags, but for
>> the current
>> implementation, I cannot see how clk-mux can be exploited here.
>>
>
> There are a couple of ways to get past this issue.  One is removal of
> some of the flags.  I have never liked CLK_SET_RATE_PARENT, since I
> think the ability to propagate a rate-change request up to the parent
> should be enabled for all clocks.  This is in the interest of the driver
> author who does not care to know intimate details of the clock
> hierarchy.
>
> When I developed the rate-change propagation code I was likely too hasty
> in defining a per-clock flag for that behavior.  It might have been
> enough to simply compare the&best_parent_rate value from .round_rate
> and compare it against clk->parent->rate.  This means that no flag is
> necessary.
>
> This assumes that the .round_rate implementation has enough knowledge to
> know whether or not to propagate the rate-change request up to the
> parent.  This may not not true for the common divider type.
>
> I'll make some tests on removing this flag (and potentially other flags)
> to see how painful it is.

Mike,

another good thing would be to have implementation independent building 
blocks for clk drivers. I think clk-fixed-rate, clk-gate, clk-mux and
some clk-synth should be enough. The current core clk templates in the
api are almost all special implementations.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-08 16:46         ` Sebastian Hesselbarth
  (?)
@ 2013-04-10  9:40           ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-10  9:40 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	Michal Bachraty, devicetree-discuss, linux-doc, linux-kernel,
	linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allows to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
I consider this v7 now as feature complete. Please review/test a
hopefully last time and add your Acked-By/Tested-By. Thanks!

Changes from v6:
- make invalid DT data parsing fatal (Suggested by Guenter Roeck)
- add more variant checks and return errors
- remove inline from _si5351_* functions
- remove pll reset/clkout powerdown for gapless tuning
  (Provided by Michal Backraty)

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1506 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 ++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1900 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..4e2506c
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1506 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return 0;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C &&
+	    parent != SI5351_PLL_SRC_XTAL)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+	return 0;
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return 0;
+
+	if (num > 8)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+	return 0;
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+			       SI5351_MULTISYNTH_SRC_VCO1);
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+	return 0;
+}
+
+static int _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+	return 0;
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			return -EINVAL;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p) {
+			dev_err(&client->dev,
+				"missing pll-source for pll %d\n", num);
+			return -EINVAL;
+		}
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			if (pdata->variant != SI5351_VARIANT_C) {
+				dev_err(&client->dev,
+					"invalid parent %d for pll %d\n",
+					val, num);
+				return -EINVAL;
+			}
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_err(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			return -EINVAL;
+		}
+	}
+
+	/* per clkout properties */
+	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 >= 8 ||
+		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
+			dev_err(&client->dev, "invalid clkout %d\n", num);
+			return -EINVAL;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for multisynth %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				if (pdata->variant != SI5351_VARIANT_C) {
+					dev_err(&client->dev,
+						"invalid parent %d for clkout %d\n",
+						val, num);
+					return -EINVAL;
+				}
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid drive strength %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++) {
+		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent pll %d to %d\n",
+				n, pdata->pll_src[n]);
+			return ret;
+		}
+	}
+
+	for (n = 0; n < 8; n++) {
+		ret = _si5351_msynth_reparent(drvdata, n,
+					      pdata->clkout[n].multisynth_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent multisynth %d to %d\n",
+				n, pdata->clkout[n].multisynth_src);
+			return ret;
+		}
+
+		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
+							pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive strength of clkout%d to %d\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..92dabca
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-10  9:40           ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-10  9:40 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	Michal Bachraty, devicetree-discuss, linux-doc, linux-kernel,
	linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allows to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
I consider this v7 now as feature complete. Please review/test a
hopefully last time and add your Acked-By/Tested-By. Thanks!

Changes from v6:
- make invalid DT data parsing fatal (Suggested by Guenter Roeck)
- add more variant checks and return errors
- remove inline from _si5351_* functions
- remove pll reset/clkout powerdown for gapless tuning
  (Provided by Michal Backraty)

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1506 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 ++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1900 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..4e2506c
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1506 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return 0;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C &&
+	    parent != SI5351_PLL_SRC_XTAL)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+	return 0;
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return 0;
+
+	if (num > 8)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+	return 0;
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+			       SI5351_MULTISYNTH_SRC_VCO1);
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+	return 0;
+}
+
+static int _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+	return 0;
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			return -EINVAL;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p) {
+			dev_err(&client->dev,
+				"missing pll-source for pll %d\n", num);
+			return -EINVAL;
+		}
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			if (pdata->variant != SI5351_VARIANT_C) {
+				dev_err(&client->dev,
+					"invalid parent %d for pll %d\n",
+					val, num);
+				return -EINVAL;
+			}
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_err(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			return -EINVAL;
+		}
+	}
+
+	/* per clkout properties */
+	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 >= 8 ||
+		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
+			dev_err(&client->dev, "invalid clkout %d\n", num);
+			return -EINVAL;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for multisynth %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				if (pdata->variant != SI5351_VARIANT_C) {
+					dev_err(&client->dev,
+						"invalid parent %d for clkout %d\n",
+						val, num);
+					return -EINVAL;
+				}
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid drive strength %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++) {
+		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent pll %d to %d\n",
+				n, pdata->pll_src[n]);
+			return ret;
+		}
+	}
+
+	for (n = 0; n < 8; n++) {
+		ret = _si5351_msynth_reparent(drvdata, n,
+					      pdata->clkout[n].multisynth_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent multisynth %d to %d\n",
+				n, pdata->clkout[n].multisynth_src);
+			return ret;
+		}
+
+		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
+							pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive strength of clkout%d to %d\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..92dabca
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-10  9:40           ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-10  9:40 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allows to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
---
I consider this v7 now as feature complete. Please review/test a
hopefully last time and add your Acked-By/Tested-By. Thanks!

Changes from v6:
- make invalid DT data parsing fatal (Suggested by Guenter Roeck)
- add more variant checks and return errors
- remove inline from _si5351_* functions
- remove pll reset/clkout powerdown for gapless tuning
  (Provided by Michal Backraty)

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1506 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 ++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1900 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..4e2506c
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1506 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return 0;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C &&
+	    parent != SI5351_PLL_SRC_XTAL)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+	return 0;
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return 0;
+
+	if (num > 8)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+	return 0;
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+			       SI5351_MULTISYNTH_SRC_VCO1);
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+	return 0;
+}
+
+static int _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+	return 0;
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			return -EINVAL;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p) {
+			dev_err(&client->dev,
+				"missing pll-source for pll %d\n", num);
+			return -EINVAL;
+		}
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			if (pdata->variant != SI5351_VARIANT_C) {
+				dev_err(&client->dev,
+					"invalid parent %d for pll %d\n",
+					val, num);
+				return -EINVAL;
+			}
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_err(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			return -EINVAL;
+		}
+	}
+
+	/* per clkout properties */
+	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 >= 8 ||
+		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
+			dev_err(&client->dev, "invalid clkout %d\n", num);
+			return -EINVAL;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for multisynth %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				if (pdata->variant != SI5351_VARIANT_C) {
+					dev_err(&client->dev,
+						"invalid parent %d for clkout %d\n",
+						val, num);
+					return -EINVAL;
+				}
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid drive strength %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++) {
+		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent pll %d to %d\n",
+				n, pdata->pll_src[n]);
+			return ret;
+		}
+	}
+
+	for (n = 0; n < 8; n++) {
+		ret = _si5351_msynth_reparent(drvdata, n,
+					      pdata->clkout[n].multisynth_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent multisynth %d to %d\n",
+				n, pdata->clkout[n].multisynth_src);
+			return ret;
+		}
+
+		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
+							pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive strength of clkout%d to %d\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..92dabca
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-10  9:40           ` Sebastian Hesselbarth
@ 2013-04-10 10:17             ` Daniel Mack
  -1 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-04-10 10:17 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Jean-Francois Moine,
	Lars-Peter Clausen, Guenter Roeck, Michal Bachraty,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On 10.04.2013 11:40, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> I consider this v7 now as feature complete. Please review/test a
> hopefully last time and add your Acked-By/Tested-By. Thanks!

Tested-by: Daniel Mack <zonque@gmail.com>


Thanks again,
Daniel


> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
> - check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
> - clean up i2c client init (Reported by Lars-Peter Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1506 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1900 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator@60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		silabs,pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			silabs,drive-strength = <8>;
> +			silabs,multisynth-source = <0>;
> +			silabs,clock-source = <0>;
> +			silabs,pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			silabs,drive-strength = <4>;
> +			silabs,multisynth-source = <1>;
> +			silabs,clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			silabs,clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..4e2506c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1506 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	const char		*pxtal_name;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	const char		*pclkin_name;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
> +		return 0;
> +	}
> +
> +	return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 count, u8 *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +				    u8 reg, u8 count, const u8 *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +				  u8 reg, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +				   u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +				    u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				int num, enum si5351_pll_src parent)
> +{
> +	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (parent == SI5351_PLL_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 2)
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C &&
> +	    parent != SI5351_PLL_SRC_XTAL)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +	return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +			     (index == 0) ? SI5351_PLL_SRC_XTAL :
> +			     SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_multisynth_src parent)
> +{
> +	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +			SI5351_CLK_PLL_SELECT);
> +	return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +			       SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_clkout_src parent)
> +{
> +	u8 val;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case SI5351_CLKOUT_SRC_MSYNTH_N:
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (num == 0 || num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case SI5351_CLKOUT_SRC_XTAL:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case SI5351_CLKOUT_SRC_CLKIN:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_INPUT_MASK, val);
> +	return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +	struct si5351_driver_data *drvdata, int num,
> +	enum si5351_drive_strength drive)
> +{
> +	u8 mask;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case SI5351_DRIVE_2MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +		break;
> +	case SI5351_DRIVE_4MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +		break;
> +	case SI5351_DRIVE_6MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +		break;
> +	case SI5351_DRIVE_8MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +	return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +	switch (index) {
> +	case 0:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +		break;
> +	case 1:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +		break;
> +	case 2:
> +		parent = SI5351_CLKOUT_SRC_XTAL;
> +		break;
> +	case 3:
> +		parent = SI5351_CLKOUT_SRC_CLKIN;
> +		break;
> +	}
> +
> +	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			return -EINVAL;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p) {
> +			dev_err(&client->dev,
> +				"missing pll-source for pll %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			if (pdata->variant != SI5351_VARIANT_C) {
> +				dev_err(&client->dev,
> +					"invalid parent %d for pll %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_err(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* per clkout properties */
> +	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 >= 8 ||
> +		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +			dev_err(&client->dev, "invalid clkout %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for multisynth %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				if (pdata->variant != SI5351_VARIANT_C) {
> +					dev_err(&client->dev,
> +						"invalid parent %d for clkout %d\n",
> +						val, num);
> +					return -EINVAL;
> +				}
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid drive strength %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++) {
> +		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent pll %d to %d\n",
> +				n, pdata->pll_src[n]);
> +			return ret;
> +		}
> +	}
> +
> +	for (n = 0; n < 8; n++) {
> +		ret = _si5351_msynth_reparent(drvdata, n,
> +					      pdata->clkout[n].multisynth_src);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent multisynth %d to %d\n",
> +				n, pdata->clkout[n].multisynth_src);
> +			return ret;
> +		}
> +
> +		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +							pdata->clkout[n].drive);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed set drive strength of clkout%d to %d\n",
> +				n, pdata->clkout[n].drive);
> +			return ret;
> +		}
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> 


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-10 10:17             ` Daniel Mack
  0 siblings, 0 replies; 95+ messages in thread
From: Daniel Mack @ 2013-04-10 10:17 UTC (permalink / raw)
  To: linux-arm-kernel

On 10.04.2013 11:40, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> I consider this v7 now as feature complete. Please review/test a
> hopefully last time and add your Acked-By/Tested-By. Thanks!

Tested-by: Daniel Mack <zonque@gmail.com>


Thanks again,
Daniel


> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
> - check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
> - clean up i2c client init (Reported by Lars-Peter Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss at lists.ozlabs.org
> Cc: linux-doc at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-arm-kernel at lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1506 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1900 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator at 60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		silabs,pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			silabs,drive-strength = <8>;
> +			silabs,multisynth-source = <0>;
> +			silabs,clock-source = <0>;
> +			silabs,pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			silabs,drive-strength = <4>;
> +			silabs,multisynth-source = <1>;
> +			silabs,clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			silabs,clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..4e2506c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1506 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	const char		*pxtal_name;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	const char		*pclkin_name;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
> +		return 0;
> +	}
> +
> +	return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 count, u8 *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +				    u8 reg, u8 count, const u8 *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +				  u8 reg, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +				   u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +				    u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				int num, enum si5351_pll_src parent)
> +{
> +	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (parent == SI5351_PLL_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 2)
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C &&
> +	    parent != SI5351_PLL_SRC_XTAL)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +	return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +			     (index == 0) ? SI5351_PLL_SRC_XTAL :
> +			     SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_multisynth_src parent)
> +{
> +	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +			SI5351_CLK_PLL_SELECT);
> +	return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +			       SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_clkout_src parent)
> +{
> +	u8 val;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case SI5351_CLKOUT_SRC_MSYNTH_N:
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (num == 0 || num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case SI5351_CLKOUT_SRC_XTAL:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case SI5351_CLKOUT_SRC_CLKIN:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_INPUT_MASK, val);
> +	return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +	struct si5351_driver_data *drvdata, int num,
> +	enum si5351_drive_strength drive)
> +{
> +	u8 mask;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case SI5351_DRIVE_2MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +		break;
> +	case SI5351_DRIVE_4MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +		break;
> +	case SI5351_DRIVE_6MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +		break;
> +	case SI5351_DRIVE_8MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +	return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +	switch (index) {
> +	case 0:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +		break;
> +	case 1:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +		break;
> +	case 2:
> +		parent = SI5351_CLKOUT_SRC_XTAL;
> +		break;
> +	case 3:
> +		parent = SI5351_CLKOUT_SRC_CLKIN;
> +		break;
> +	}
> +
> +	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			return -EINVAL;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p) {
> +			dev_err(&client->dev,
> +				"missing pll-source for pll %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			if (pdata->variant != SI5351_VARIANT_C) {
> +				dev_err(&client->dev,
> +					"invalid parent %d for pll %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_err(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* per clkout properties */
> +	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 >= 8 ||
> +		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +			dev_err(&client->dev, "invalid clkout %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for multisynth %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				if (pdata->variant != SI5351_VARIANT_C) {
> +					dev_err(&client->dev,
> +						"invalid parent %d for clkout %d\n",
> +						val, num);
> +					return -EINVAL;
> +				}
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid drive strength %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++) {
> +		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent pll %d to %d\n",
> +				n, pdata->pll_src[n]);
> +			return ret;
> +		}
> +	}
> +
> +	for (n = 0; n < 8; n++) {
> +		ret = _si5351_msynth_reparent(drvdata, n,
> +					      pdata->clkout[n].multisynth_src);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent multisynth %d to %d\n",
> +				n, pdata->clkout[n].multisynth_src);
> +			return ret;
> +		}
> +
> +		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +							pdata->clkout[n].drive);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed set drive strength of clkout%d to %d\n",
> +				n, pdata->clkout[n].drive);
> +			return ret;
> +		}
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-10 10:17             ` Daniel Mack
@ 2013-04-10 14:48               ` Michal Bachraty
  -1 siblings, 0 replies; 95+ messages in thread
From: Michal Bachraty @ 2013-04-10 14:48 UTC (permalink / raw)
  To: Daniel Mack
  Cc: Sebastian Hesselbarth, Grant Likely, Rob Herring, Rob Landley,
	Mike Turquette, Stephen Warren, Thierry Reding, Dom Cobley,
	Linus Walleij, Arnd Bergmann, Andrew Morton, Pawel Moll,
	Mark Brown, Russell King - ARM Linux, Rabeeh Khoury,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Hi Sebastian,
This driver doesn't work for me. In my case, u-boot initializes si-5351 and 
power down unused clocks while booting kernel.  there is need for power up 
clocks as was in previous versions of your driver.  
See patch, whre the problem is fixed:

@@ -992,6 +992,10 @@ static long si5351_clkout_round_rate(struct clk_hw *hw, 
unsigned long rate,
 		} while (1);
 	}
 	rate = *parent_rate >> rdiv;
+	
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
 
 	dev_dbg(&hwdata->drvdata->client->dev,
 		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",

With this lines, driver works well.

Also, 

> > +==Example==
> > +
> > +/* 25MHz reference crystal */
> > +ref25: ref25M {
> > +	compatible = "fixed-clock";
> > +	#clock-cells = <0>;
> > +	clock-frequency = <25000000>;
> > +};
> > +
> > +i2c-master-node {
> > +
> > +	/* Si5351a msop10 i2c clock generator */
> > +	si5351a: clock-generator@60 {
> > +		compatible = "silabs,si5351a-msop";
> > +		reg = <0x60>;
> > +		#address-cells = <1>;
> > +		#size-cells = <0>;
> > +		#clock-cells = <1>;
> > +
> > +		/* connect xtal input to 25MHz reference */
> > +		clocks = <&ref25>;
> > +
> > +		/* connect xtal input as source of pll0 and pll1 */
> > +		silabs,pll-source = <0 0>, <1 0>;
> > +
> > +		/*
> > +		 * overwrite clkout0 configuration with:
> > +		 * - 8mA output drive strength
> > +		 * - pll0 as clock source of multisynth0
> > +		 * - multisynth0 as clock source of output divider
> > +		 * - multisynth0 can change pll0
> > +		 * - set initial clock frequency of 74.25MHz
> > +		 */
> > +		clkout0 {
> > +			reg = <0>;
> > +			silabs,drive-strength = <8>;
> > +			silabs,multisynth-source = <0>;
> > +			silabs,clock-source = <0>;
> > +			silabs,pll-master;
> > +			clock-frequency = <74250000>;
> > +		};
> > +
> > +		/*
> > +		 * overwrite clkout1 configuration with:
> > +		 * - 4mA output drive strength
> > +		 * - pll1 as clock source of multisynth1
> > +		 * - multisynth1 as clock source of output divider
> > +		 * - multisynth1 can change pll1
> > +		 */
> > +		clkout1 {
> > +			reg = <1>;
> > +			silabs,drive-strength = <4>;
> > +			silabs,multisynth-source = <1>;
> > +			silabs,clock-source = <0>;
> > +			pll-master;
> > +		};
> > +
 Is definition of pll-master in clkout1 correct? should not be silabs,pll-
master ?

Best regards,
Michal

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-10 14:48               ` Michal Bachraty
  0 siblings, 0 replies; 95+ messages in thread
From: Michal Bachraty @ 2013-04-10 14:48 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sebastian,
This driver doesn't work for me. In my case, u-boot initializes si-5351 and 
power down unused clocks while booting kernel.  there is need for power up 
clocks as was in previous versions of your driver.  
See patch, whre the problem is fixed:

@@ -992,6 +992,10 @@ static long si5351_clkout_round_rate(struct clk_hw *hw, 
unsigned long rate,
 		} while (1);
 	}
 	rate = *parent_rate >> rdiv;
+	
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
 
 	dev_dbg(&hwdata->drvdata->client->dev,
 		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",

With this lines, driver works well.

Also, 

> > +==Example==
> > +
> > +/* 25MHz reference crystal */
> > +ref25: ref25M {
> > +	compatible = "fixed-clock";
> > +	#clock-cells = <0>;
> > +	clock-frequency = <25000000>;
> > +};
> > +
> > +i2c-master-node {
> > +
> > +	/* Si5351a msop10 i2c clock generator */
> > +	si5351a: clock-generator at 60 {
> > +		compatible = "silabs,si5351a-msop";
> > +		reg = <0x60>;
> > +		#address-cells = <1>;
> > +		#size-cells = <0>;
> > +		#clock-cells = <1>;
> > +
> > +		/* connect xtal input to 25MHz reference */
> > +		clocks = <&ref25>;
> > +
> > +		/* connect xtal input as source of pll0 and pll1 */
> > +		silabs,pll-source = <0 0>, <1 0>;
> > +
> > +		/*
> > +		 * overwrite clkout0 configuration with:
> > +		 * - 8mA output drive strength
> > +		 * - pll0 as clock source of multisynth0
> > +		 * - multisynth0 as clock source of output divider
> > +		 * - multisynth0 can change pll0
> > +		 * - set initial clock frequency of 74.25MHz
> > +		 */
> > +		clkout0 {
> > +			reg = <0>;
> > +			silabs,drive-strength = <8>;
> > +			silabs,multisynth-source = <0>;
> > +			silabs,clock-source = <0>;
> > +			silabs,pll-master;
> > +			clock-frequency = <74250000>;
> > +		};
> > +
> > +		/*
> > +		 * overwrite clkout1 configuration with:
> > +		 * - 4mA output drive strength
> > +		 * - pll1 as clock source of multisynth1
> > +		 * - multisynth1 as clock source of output divider
> > +		 * - multisynth1 can change pll1
> > +		 */
> > +		clkout1 {
> > +			reg = <1>;
> > +			silabs,drive-strength = <4>;
> > +			silabs,multisynth-source = <1>;
> > +			silabs,clock-source = <0>;
> > +			pll-master;
> > +		};
> > +
 Is definition of pll-master in clkout1 correct? should not be silabs,pll-
master ?

Best regards,
Michal

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-10 14:48               ` Michal Bachraty
  (?)
@ 2013-04-10 16:34               ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-10 16:34 UTC (permalink / raw)
  To: Michal Bachraty
  Cc: linux-doc-u79uwXL29TY76Z2rM5mHXA, Lars-Peter Clausen,
	Stephen Warren, Mike Turquette, Rabeeh Khoury, Guenter Roeck,
	Pawel Moll, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	Rob Herring, Dom Cobley, Russell King - ARM Linux,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	Jean-Francois Moine, Mark Brown,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Andrew Morton


[-- Attachment #1.1: Type: text/plain, Size: 3444 bytes --]

On Wed, Apr 10, 2013 at 4:48 PM, Michal Bachraty <
michal.bachraty-6oiIBCxl0MMjD8S081q9vkEOCMrvLtNR@public.gmane.org> wrote:

> Hi Sebastian,
> This driver doesn't work for me. In my case, u-boot initializes si-5351 and
> power down unused clocks while booting kernel.  there is need for power up
> clocks as was in previous versions of your driver.
> See patch, whre the problem is fixed:
>
> @@ -992,6 +992,10 @@ static long si5351_clkout_round_rate(struct clk_hw
> *hw,
> unsigned long rate,
>                 } while (1);
>         }
>         rate = *parent_rate >> rdiv;
> +
> +       /* powerup clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
>
>         dev_dbg(&hwdata->drvdata->client->dev,
>                 "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
>
> With this lines, driver works well.
>

Hmm, is there any driver using the clock output? Does it
clk_prepare_enable() the clock?
I tend not to mess with anything the bootloader or eeprom config left
disabled. It works
for me, but here the driver will prepare/enable the clock prior use.


> Also,
>
> > > +==Example==
> > > +
> > > +/* 25MHz reference crystal */
> > > +ref25: ref25M {
> > > +   compatible = "fixed-clock";
> > > +   #clock-cells = <0>;
> > > +   clock-frequency = <25000000>;
> > > +};
> > > +
> > > +i2c-master-node {
> > > +
> > > +   /* Si5351a msop10 i2c clock generator */
> > > +   si5351a: clock-generator@60 {
> > > +           compatible = "silabs,si5351a-msop";
> > > +           reg = <0x60>;
> > > +           #address-cells = <1>;
> > > +           #size-cells = <0>;
> > > +           #clock-cells = <1>;
> > > +
> > > +           /* connect xtal input to 25MHz reference */
> > > +           clocks = <&ref25>;
> > > +
> > > +           /* connect xtal input as source of pll0 and pll1 */
> > > +           silabs,pll-source = <0 0>, <1 0>;
> > > +
> > > +           /*
> > > +            * overwrite clkout0 configuration with:
> > > +            * - 8mA output drive strength
> > > +            * - pll0 as clock source of multisynth0
> > > +            * - multisynth0 as clock source of output divider
> > > +            * - multisynth0 can change pll0
> > > +            * - set initial clock frequency of 74.25MHz
> > > +            */
> > > +           clkout0 {
> > > +                   reg = <0>;
> > > +                   silabs,drive-strength = <8>;
> > > +                   silabs,multisynth-source = <0>;
> > > +                   silabs,clock-source = <0>;
> > > +                   silabs,pll-master;
> > > +                   clock-frequency = <74250000>;
> > > +           };
> > > +
> > > +           /*
> > > +            * overwrite clkout1 configuration with:
> > > +            * - 4mA output drive strength
> > > +            * - pll1 as clock source of multisynth1
> > > +            * - multisynth1 as clock source of output divider
> > > +            * - multisynth1 can change pll1
> > > +            */
> > > +           clkout1 {
> > > +                   reg = <1>;
> > > +                   silabs,drive-strength = <4>;
> > > +                   silabs,multisynth-source = <1>;
> > > +                   silabs,clock-source = <0>;
> > > +                   pll-master;
> > > +           };
> > > +
>  Is definition of pll-master in clkout1 correct? should not be silabs,pll-
> master ?
>

Good catch.

Sebastian

[-- Attachment #1.2: Type: text/html, Size: 4849 bytes --]

[-- Attachment #2: Type: text/plain, Size: 192 bytes --]

_______________________________________________
devicetree-discuss mailing list
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org
https://lists.ozlabs.org/listinfo/devicetree-discuss

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-10 14:48               ` Michal Bachraty
  (?)
@ 2013-04-10 17:27                 ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-10 17:27 UTC (permalink / raw)
  To: Michal Bachraty
  Cc: Daniel Mack, Grant Likely, Rob Herring, Rob Landley,
	Mike Turquette, Stephen Warren, Thierry Reding, Dom Cobley,
	Linus Walleij, Arnd Bergmann, Andrew Morton, Pawel Moll,
	Mark Brown, Russell King - ARM Linux, Rabeeh Khoury,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Hooray Google!

Thanks for removing plain text sending from gmail web-frontend.. *sigh*
Sorry for resending this, but HTML mails get rejected by linux mailing lists.

On 04/10/2013 04:48 PM, Michal Bachraty wrote:
> Hi Sebastian,
> This driver doesn't work for me. In my case, u-boot initializes si-5351 and
> power down unused clocks while booting kernel.  there is need for power up
> clocks as was in previous versions of your driver.
> See patch, whre the problem is fixed:
>
> @@ -992,6 +992,10 @@ static long si5351_clkout_round_rate(struct clk_hw *hw,
> unsigned long rate,
>   		} while (1);
>   	}
>   	rate = *parent_rate>>  rdiv;
> +	
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
>
>   	dev_dbg(&hwdata->drvdata->client->dev,
>   		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
>
> With this lines, driver works well.

Hmm, is there any driver using the clock output? Does it clk_prepare_enable() the clock?
I tend not to mess with anything the bootloader or eeprom config left disabled. It works
for me, but here the driver will prepare/enable the clock prior use.

> Also,
>
>>> +==Example==
>>> +
>>> +/* 25MHz reference crystal */
>>> +ref25: ref25M {
>>> +	compatible = "fixed-clock";
>>> +	#clock-cells =<0>;
>>> +	clock-frequency =<25000000>;
>>> +};
>>> +
>>> +i2c-master-node {
>>> +
>>> +	/* Si5351a msop10 i2c clock generator */
>>> +	si5351a: clock-generator@60 {
>>> +		compatible = "silabs,si5351a-msop";
>>> +		reg =<0x60>;
>>> +		#address-cells =<1>;
>>> +		#size-cells =<0>;
>>> +		#clock-cells =<1>;
>>> +
>>> +		/* connect xtal input to 25MHz reference */
>>> +		clocks =<&ref25>;
>>> +
>>> +		/* connect xtal input as source of pll0 and pll1 */
>>> +		silabs,pll-source =<0 0>,<1 0>;
>>> +
>>> +		/*
>>> +		 * overwrite clkout0 configuration with:
>>> +		 * - 8mA output drive strength
>>> +		 * - pll0 as clock source of multisynth0
>>> +		 * - multisynth0 as clock source of output divider
>>> +		 * - multisynth0 can change pll0
>>> +		 * - set initial clock frequency of 74.25MHz
>>> +		 */
>>> +		clkout0 {
>>> +			reg =<0>;
>>> +			silabs,drive-strength =<8>;
>>> +			silabs,multisynth-source =<0>;
>>> +			silabs,clock-source =<0>;
>>> +			silabs,pll-master;
>>> +			clock-frequency =<74250000>;
>>> +		};
>>> +
>>> +		/*
>>> +		 * overwrite clkout1 configuration with:
>>> +		 * - 4mA output drive strength
>>> +		 * - pll1 as clock source of multisynth1
>>> +		 * - multisynth1 as clock source of output divider
>>> +		 * - multisynth1 can change pll1
>>> +		 */
>>> +		clkout1 {
>>> +			reg =<1>;
>>> +			silabs,drive-strength =<4>;
>>> +			silabs,multisynth-source =<1>;
>>> +			silabs,clock-source =<0>;
>>> +			pll-master;
>>> +		};
>>> +
>   Is definition of pll-master in clkout1 correct? should not be silabs,pll-
> master ?

Good catch.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-10 17:27                 ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-10 17:27 UTC (permalink / raw)
  To: Michal Bachraty
  Cc: linux-doc, Linus Walleij, Thierry Reding, Grant Likely,
	Lars-Peter Clausen, Stephen Warren, Arnd Bergmann,
	Mike Turquette, Rabeeh Khoury, Guenter Roeck, Pawel Moll,
	devicetree-discuss, Rob Herring, Dom Cobley,
	Russell King - ARM Linux, linux-arm-kernel, Jean-Francois Moine,
	Mark Brown, linux-kernel, Daniel Mack, Rob Landley,
	Andrew Morton

Hooray Google!

Thanks for removing plain text sending from gmail web-frontend.. *sigh*
Sorry for resending this, but HTML mails get rejected by linux mailing lists.

On 04/10/2013 04:48 PM, Michal Bachraty wrote:
> Hi Sebastian,
> This driver doesn't work for me. In my case, u-boot initializes si-5351 and
> power down unused clocks while booting kernel.  there is need for power up
> clocks as was in previous versions of your driver.
> See patch, whre the problem is fixed:
>
> @@ -992,6 +992,10 @@ static long si5351_clkout_round_rate(struct clk_hw *hw,
> unsigned long rate,
>   		} while (1);
>   	}
>   	rate = *parent_rate>>  rdiv;
> +	
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
>
>   	dev_dbg(&hwdata->drvdata->client->dev,
>   		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
>
> With this lines, driver works well.

Hmm, is there any driver using the clock output? Does it clk_prepare_enable() the clock?
I tend not to mess with anything the bootloader or eeprom config left disabled. It works
for me, but here the driver will prepare/enable the clock prior use.

> Also,
>
>>> +==Example==
>>> +
>>> +/* 25MHz reference crystal */
>>> +ref25: ref25M {
>>> +	compatible = "fixed-clock";
>>> +	#clock-cells =<0>;
>>> +	clock-frequency =<25000000>;
>>> +};
>>> +
>>> +i2c-master-node {
>>> +
>>> +	/* Si5351a msop10 i2c clock generator */
>>> +	si5351a: clock-generator@60 {
>>> +		compatible = "silabs,si5351a-msop";
>>> +		reg =<0x60>;
>>> +		#address-cells =<1>;
>>> +		#size-cells =<0>;
>>> +		#clock-cells =<1>;
>>> +
>>> +		/* connect xtal input to 25MHz reference */
>>> +		clocks =<&ref25>;
>>> +
>>> +		/* connect xtal input as source of pll0 and pll1 */
>>> +		silabs,pll-source =<0 0>,<1 0>;
>>> +
>>> +		/*
>>> +		 * overwrite clkout0 configuration with:
>>> +		 * - 8mA output drive strength
>>> +		 * - pll0 as clock source of multisynth0
>>> +		 * - multisynth0 as clock source of output divider
>>> +		 * - multisynth0 can change pll0
>>> +		 * - set initial clock frequency of 74.25MHz
>>> +		 */
>>> +		clkout0 {
>>> +			reg =<0>;
>>> +			silabs,drive-strength =<8>;
>>> +			silabs,multisynth-source =<0>;
>>> +			silabs,clock-source =<0>;
>>> +			silabs,pll-master;
>>> +			clock-frequency =<74250000>;
>>> +		};
>>> +
>>> +		/*
>>> +		 * overwrite clkout1 configuration with:
>>> +		 * - 4mA output drive strength
>>> +		 * - pll1 as clock source of multisynth1
>>> +		 * - multisynth1 as clock source of output divider
>>> +		 * - multisynth1 can change pll1
>>> +		 */
>>> +		clkout1 {
>>> +			reg =<1>;
>>> +			silabs,drive-strength =<4>;
>>> +			silabs,multisynth-source =<1>;
>>> +			silabs,clock-source =<0>;
>>> +			pll-master;
>>> +		};
>>> +
>   Is definition of pll-master in clkout1 correct? should not be silabs,pll-
> master ?

Good catch.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-10 17:27                 ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-10 17:27 UTC (permalink / raw)
  To: linux-arm-kernel

Hooray Google!

Thanks for removing plain text sending from gmail web-frontend.. *sigh*
Sorry for resending this, but HTML mails get rejected by linux mailing lists.

On 04/10/2013 04:48 PM, Michal Bachraty wrote:
> Hi Sebastian,
> This driver doesn't work for me. In my case, u-boot initializes si-5351 and
> power down unused clocks while booting kernel.  there is need for power up
> clocks as was in previous versions of your driver.
> See patch, whre the problem is fixed:
>
> @@ -992,6 +992,10 @@ static long si5351_clkout_round_rate(struct clk_hw *hw,
> unsigned long rate,
>   		} while (1);
>   	}
>   	rate = *parent_rate>>  rdiv;
> +	
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
>
>   	dev_dbg(&hwdata->drvdata->client->dev,
>   		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
>
> With this lines, driver works well.

Hmm, is there any driver using the clock output? Does it clk_prepare_enable() the clock?
I tend not to mess with anything the bootloader or eeprom config left disabled. It works
for me, but here the driver will prepare/enable the clock prior use.

> Also,
>
>>> +==Example==
>>> +
>>> +/* 25MHz reference crystal */
>>> +ref25: ref25M {
>>> +	compatible = "fixed-clock";
>>> +	#clock-cells =<0>;
>>> +	clock-frequency =<25000000>;
>>> +};
>>> +
>>> +i2c-master-node {
>>> +
>>> +	/* Si5351a msop10 i2c clock generator */
>>> +	si5351a: clock-generator at 60 {
>>> +		compatible = "silabs,si5351a-msop";
>>> +		reg =<0x60>;
>>> +		#address-cells =<1>;
>>> +		#size-cells =<0>;
>>> +		#clock-cells =<1>;
>>> +
>>> +		/* connect xtal input to 25MHz reference */
>>> +		clocks =<&ref25>;
>>> +
>>> +		/* connect xtal input as source of pll0 and pll1 */
>>> +		silabs,pll-source =<0 0>,<1 0>;
>>> +
>>> +		/*
>>> +		 * overwrite clkout0 configuration with:
>>> +		 * - 8mA output drive strength
>>> +		 * - pll0 as clock source of multisynth0
>>> +		 * - multisynth0 as clock source of output divider
>>> +		 * - multisynth0 can change pll0
>>> +		 * - set initial clock frequency of 74.25MHz
>>> +		 */
>>> +		clkout0 {
>>> +			reg =<0>;
>>> +			silabs,drive-strength =<8>;
>>> +			silabs,multisynth-source =<0>;
>>> +			silabs,clock-source =<0>;
>>> +			silabs,pll-master;
>>> +			clock-frequency =<74250000>;
>>> +		};
>>> +
>>> +		/*
>>> +		 * overwrite clkout1 configuration with:
>>> +		 * - 4mA output drive strength
>>> +		 * - pll1 as clock source of multisynth1
>>> +		 * - multisynth1 as clock source of output divider
>>> +		 * - multisynth1 can change pll1
>>> +		 */
>>> +		clkout1 {
>>> +			reg =<1>;
>>> +			silabs,drive-strength =<4>;
>>> +			silabs,multisynth-source =<1>;
>>> +			silabs,clock-source =<0>;
>>> +			pll-master;
>>> +		};
>>> +
>   Is definition of pll-master in clkout1 correct? should not be silabs,pll-
> master ?

Good catch.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-10  9:40           ` Sebastian Hesselbarth
@ 2013-04-10 19:34             ` Guenter Roeck
  -1 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-10 19:34 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, Michal Bachraty,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On Wed, Apr 10, 2013 at 11:40:10AM +0200, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> I consider this v7 now as feature complete. Please review/test a
> hopefully last time and add your Acked-By/Tested-By. Thanks!
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
> - check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
> - clean up i2c client init (Reported by Lars-Peter Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>

Acked-by: Guenter Roeck <linux@roeck-us.net>

> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1506 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1900 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator@60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		silabs,pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			silabs,drive-strength = <8>;
> +			silabs,multisynth-source = <0>;
> +			silabs,clock-source = <0>;
> +			silabs,pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			silabs,drive-strength = <4>;
> +			silabs,multisynth-source = <1>;
> +			silabs,clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			silabs,clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..4e2506c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1506 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	const char		*pxtal_name;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	const char		*pclkin_name;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
> +		return 0;
> +	}
> +
> +	return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 count, u8 *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +				    u8 reg, u8 count, const u8 *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +				  u8 reg, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +				   u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +				    u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				int num, enum si5351_pll_src parent)
> +{
> +	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (parent == SI5351_PLL_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 2)
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C &&
> +	    parent != SI5351_PLL_SRC_XTAL)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +	return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +			     (index == 0) ? SI5351_PLL_SRC_XTAL :
> +			     SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_multisynth_src parent)
> +{
> +	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +			SI5351_CLK_PLL_SELECT);
> +	return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +			       SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_clkout_src parent)
> +{
> +	u8 val;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case SI5351_CLKOUT_SRC_MSYNTH_N:
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (num == 0 || num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case SI5351_CLKOUT_SRC_XTAL:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case SI5351_CLKOUT_SRC_CLKIN:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_INPUT_MASK, val);
> +	return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +	struct si5351_driver_data *drvdata, int num,
> +	enum si5351_drive_strength drive)
> +{
> +	u8 mask;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case SI5351_DRIVE_2MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +		break;
> +	case SI5351_DRIVE_4MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +		break;
> +	case SI5351_DRIVE_6MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +		break;
> +	case SI5351_DRIVE_8MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +	return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +	switch (index) {
> +	case 0:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +		break;
> +	case 1:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +		break;
> +	case 2:
> +		parent = SI5351_CLKOUT_SRC_XTAL;
> +		break;
> +	case 3:
> +		parent = SI5351_CLKOUT_SRC_CLKIN;
> +		break;
> +	}
> +
> +	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			return -EINVAL;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p) {
> +			dev_err(&client->dev,
> +				"missing pll-source for pll %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			if (pdata->variant != SI5351_VARIANT_C) {
> +				dev_err(&client->dev,
> +					"invalid parent %d for pll %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_err(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* per clkout properties */
> +	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 >= 8 ||
> +		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +			dev_err(&client->dev, "invalid clkout %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for multisynth %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				if (pdata->variant != SI5351_VARIANT_C) {
> +					dev_err(&client->dev,
> +						"invalid parent %d for clkout %d\n",
> +						val, num);
> +					return -EINVAL;
> +				}
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid drive strength %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++) {
> +		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent pll %d to %d\n",
> +				n, pdata->pll_src[n]);
> +			return ret;
> +		}
> +	}
> +
> +	for (n = 0; n < 8; n++) {
> +		ret = _si5351_msynth_reparent(drvdata, n,
> +					      pdata->clkout[n].multisynth_src);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent multisynth %d to %d\n",
> +				n, pdata->clkout[n].multisynth_src);
> +			return ret;
> +		}
> +
> +		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +							pdata->clkout[n].drive);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed set drive strength of clkout%d to %d\n",
> +				n, pdata->clkout[n].drive);
> +			return ret;
> +		}
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> -- 
> 1.7.10.4
> 
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-10 19:34             ` Guenter Roeck
  0 siblings, 0 replies; 95+ messages in thread
From: Guenter Roeck @ 2013-04-10 19:34 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Apr 10, 2013 at 11:40:10AM +0200, Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> I consider this v7 now as feature complete. Please review/test a
> hopefully last time and add your Acked-By/Tested-By. Thanks!
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
> - check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
> - clean up i2c client init (Reported by Lars-Peter Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>

Acked-by: Guenter Roeck <linux@roeck-us.net>

> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss at lists.ozlabs.org
> Cc: linux-doc at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-arm-kernel at lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1506 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1900 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator at 60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		silabs,pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			silabs,drive-strength = <8>;
> +			silabs,multisynth-source = <0>;
> +			silabs,clock-source = <0>;
> +			silabs,pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			silabs,drive-strength = <4>;
> +			silabs,multisynth-source = <1>;
> +			silabs,clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			silabs,clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..4e2506c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1506 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	const char		*pxtal_name;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	const char		*pclkin_name;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
> +		return 0;
> +	}
> +
> +	return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 count, u8 *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +				    u8 reg, u8 count, const u8 *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +				  u8 reg, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +				   u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +				    u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				int num, enum si5351_pll_src parent)
> +{
> +	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (parent == SI5351_PLL_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 2)
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C &&
> +	    parent != SI5351_PLL_SRC_XTAL)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +	return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +			     (index == 0) ? SI5351_PLL_SRC_XTAL :
> +			     SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_multisynth_src parent)
> +{
> +	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +			SI5351_CLK_PLL_SELECT);
> +	return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +			       SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_clkout_src parent)
> +{
> +	u8 val;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case SI5351_CLKOUT_SRC_MSYNTH_N:
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (num == 0 || num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case SI5351_CLKOUT_SRC_XTAL:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case SI5351_CLKOUT_SRC_CLKIN:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_INPUT_MASK, val);
> +	return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +	struct si5351_driver_data *drvdata, int num,
> +	enum si5351_drive_strength drive)
> +{
> +	u8 mask;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case SI5351_DRIVE_2MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +		break;
> +	case SI5351_DRIVE_4MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +		break;
> +	case SI5351_DRIVE_6MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +		break;
> +	case SI5351_DRIVE_8MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +	return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +	switch (index) {
> +	case 0:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +		break;
> +	case 1:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +		break;
> +	case 2:
> +		parent = SI5351_CLKOUT_SRC_XTAL;
> +		break;
> +	case 3:
> +		parent = SI5351_CLKOUT_SRC_CLKIN;
> +		break;
> +	}
> +
> +	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			return -EINVAL;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p) {
> +			dev_err(&client->dev,
> +				"missing pll-source for pll %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			if (pdata->variant != SI5351_VARIANT_C) {
> +				dev_err(&client->dev,
> +					"invalid parent %d for pll %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_err(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* per clkout properties */
> +	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 >= 8 ||
> +		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +			dev_err(&client->dev, "invalid clkout %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for multisynth %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				if (pdata->variant != SI5351_VARIANT_C) {
> +					dev_err(&client->dev,
> +						"invalid parent %d for clkout %d\n",
> +						val, num);
> +					return -EINVAL;
> +				}
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid drive strength %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++) {
> +		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent pll %d to %d\n",
> +				n, pdata->pll_src[n]);
> +			return ret;
> +		}
> +	}
> +
> +	for (n = 0; n < 8; n++) {
> +		ret = _si5351_msynth_reparent(drvdata, n,
> +					      pdata->clkout[n].multisynth_src);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent multisynth %d to %d\n",
> +				n, pdata->clkout[n].multisynth_src);
> +			return ret;
> +		}
> +
> +		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +							pdata->clkout[n].drive);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed set drive strength of clkout%d to %d\n",
> +				n, pdata->clkout[n].drive);
> +			return ret;
> +		}
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> -- 
> 1.7.10.4
> 
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-10 17:27                 ` Sebastian Hesselbarth
@ 2013-04-11  7:44                   ` Michal Bachraty
  -1 siblings, 0 replies; 95+ messages in thread
From: Michal Bachraty @ 2013-04-11  7:44 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Daniel Mack, Grant Likely, Rob Herring, Rob Landley,
	Mike Turquette, Stephen Warren, Thierry Reding, Dom Cobley,
	Linus Walleij, Arnd Bergmann, Andrew Morton, Pawel Moll,
	Mark Brown, Russell King - ARM Linux, Rabeeh Khoury,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Hi Sebastian,

On Wednesday, April 10, 2013 19:27:25 Sebastian Hesselbarth wrote:
> Hmm, is there any driver using the clock output? Does it
> clk_prepare_enable() the clock? I tend not to mess with anything the
> bootloader or eeprom config left disabled. It works for me, but here the
> driver will prepare/enable the clock prior use.

For my case, disabled clock outputs in bootloader are correct. I'm working on 
module board with lot of purposes. For one I need to tune frequncy, for other 
not. In some cases I can use  clk_prepare_enable. For now, I need to tune clk, 
so I can't use clk_prepare_enable. Si5351 chip has no problem with enabling 
clock output multiple times, when calling clk_set_rate. So if this feature 
will not be in driver, I need to stay with patch upon driver.

Many thaks for writting driver,

Michal


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-11  7:44                   ` Michal Bachraty
  0 siblings, 0 replies; 95+ messages in thread
From: Michal Bachraty @ 2013-04-11  7:44 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sebastian,

On Wednesday, April 10, 2013 19:27:25 Sebastian Hesselbarth wrote:
> Hmm, is there any driver using the clock output? Does it
> clk_prepare_enable() the clock? I tend not to mess with anything the
> bootloader or eeprom config left disabled. It works for me, but here the
> driver will prepare/enable the clock prior use.

For my case, disabled clock outputs in bootloader are correct. I'm working on 
module board with lot of purposes. For one I need to tune frequncy, for other 
not. In some cases I can use  clk_prepare_enable. For now, I need to tune clk, 
so I can't use clk_prepare_enable. Si5351 chip has no problem with enabling 
clock output multiple times, when calling clk_set_rate. So if this feature 
will not be in driver, I need to stay with patch upon driver.

Many thaks for writting driver,

Michal

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v7] clk: add si5351 i2c common clock driver
  2013-04-11  7:44                   ` Michal Bachraty
@ 2013-04-11  8:22                     ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-11  8:22 UTC (permalink / raw)
  To: Michal Bachraty
  Cc: Daniel Mack, Grant Likely, Rob Herring, Rob Landley,
	Mike Turquette, Stephen Warren, Thierry Reding, Dom Cobley,
	Linus Walleij, Arnd Bergmann, Andrew Morton, Pawel Moll,
	Mark Brown, Russell King - ARM Linux, Rabeeh Khoury,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On Thu, Apr 11, 2013 at 9:44 AM, Michal Bachraty
<michal.bachraty@streamunlimited.com> wrote:
> On Wednesday, April 10, 2013 19:27:25 Sebastian Hesselbarth wrote:
>> Hmm, is there any driver using the clock output? Does it
>> clk_prepare_enable() the clock? I tend not to mess with anything the
>> bootloader or eeprom config left disabled. It works for me, but here the
>> driver will prepare/enable the clock prior use.
>
> For my case, disabled clock outputs in bootloader are correct. I'm working on
> module board with lot of purposes. For one I need to tune frequncy, for other
> not. In some cases I can use  clk_prepare_enable. For now, I need to tune clk,
> so I can't use clk_prepare_enable. Si5351 chip has no problem with enabling
> clock output multiple times, when calling clk_set_rate. So if this feature
> will not be in driver, I need to stay with patch upon driver.

Michal,

after thinking about it, I will re-add the power-on on rate change.
Not because I
want to have a workaround for not using clk_prepare_enable() before
clk_set_rate(),
but clkout will remain disabled if you set clock-frequency by DT.
There will be no
superfluous writes to i2c resisters anyway, because clk-si5351 is
using regmap-i2c
that takes care of caching.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7] clk: add si5351 i2c common clock driver
@ 2013-04-11  8:22                     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-11  8:22 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Apr 11, 2013 at 9:44 AM, Michal Bachraty
<michal.bachraty@streamunlimited.com> wrote:
> On Wednesday, April 10, 2013 19:27:25 Sebastian Hesselbarth wrote:
>> Hmm, is there any driver using the clock output? Does it
>> clk_prepare_enable() the clock? I tend not to mess with anything the
>> bootloader or eeprom config left disabled. It works for me, but here the
>> driver will prepare/enable the clock prior use.
>
> For my case, disabled clock outputs in bootloader are correct. I'm working on
> module board with lot of purposes. For one I need to tune frequncy, for other
> not. In some cases I can use  clk_prepare_enable. For now, I need to tune clk,
> so I can't use clk_prepare_enable. Si5351 chip has no problem with enabling
> clock output multiple times, when calling clk_set_rate. So if this feature
> will not be in driver, I need to stay with patch upon driver.

Michal,

after thinking about it, I will re-add the power-on on rate change.
Not because I
want to have a workaround for not using clk_prepare_enable() before
clk_set_rate(),
but clkout will remain disabled if you set clock-frequency by DT.
There will be no
superfluous writes to i2c resisters anyway, because clk-si5351 is
using regmap-i2c
that takes care of caching.

Sebastian

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v8] clk: add si5351 i2c common clock driver
  2013-04-10  9:40           ` Sebastian Hesselbarth
  (?)
@ 2013-04-11 19:42             ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-11 19:42 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	Michal Bachraty, devicetree-discuss, linux-doc, linux-kernel,
	linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allows to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Tested-by: Daniel Mack <zonque@gmail.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
---
Changes from v7:
- readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)

Changes from v6:
- make invalid DT data parsing fatal (Suggested by Guenter Roeck)
- add more variant checks and return errors
- remove inline from _si5351_* functions
- remove pll reset/clkout powerdown for gapless tuning
  (Provided by Michal Backraty)

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1510 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 ++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1904 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..8927284
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1510 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return 0;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C &&
+	    parent != SI5351_PLL_SRC_XTAL)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+	return 0;
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return 0;
+
+	if (num > 8)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+	return 0;
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+			       SI5351_MULTISYNTH_SRC_VCO1);
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+	return 0;
+}
+
+static int _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+	return 0;
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			return -EINVAL;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p) {
+			dev_err(&client->dev,
+				"missing pll-source for pll %d\n", num);
+			return -EINVAL;
+		}
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			if (pdata->variant != SI5351_VARIANT_C) {
+				dev_err(&client->dev,
+					"invalid parent %d for pll %d\n",
+					val, num);
+				return -EINVAL;
+			}
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_err(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			return -EINVAL;
+		}
+	}
+
+	/* per clkout properties */
+	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 >= 8 ||
+		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
+			dev_err(&client->dev, "invalid clkout %d\n", num);
+			return -EINVAL;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for multisynth %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				if (pdata->variant != SI5351_VARIANT_C) {
+					dev_err(&client->dev,
+						"invalid parent %d for clkout %d\n",
+						val, num);
+					return -EINVAL;
+				}
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid drive strength %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++) {
+		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent pll %d to %d\n",
+				n, pdata->pll_src[n]);
+			return ret;
+		}
+	}
+
+	for (n = 0; n < 8; n++) {
+		ret = _si5351_msynth_reparent(drvdata, n,
+					      pdata->clkout[n].multisynth_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent multisynth %d to %d\n",
+				n, pdata->clkout[n].multisynth_src);
+			return ret;
+		}
+
+		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
+							pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive strength of clkout%d to %d\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..92dabca
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v8] clk: add si5351 i2c common clock driver
@ 2013-04-11 19:42             ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-11 19:42 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	Michal Bachraty, devicetree-discuss, linux-doc, linux-kernel,
	linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allows to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Tested-by: Daniel Mack <zonque@gmail.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
---
Changes from v7:
- readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)

Changes from v6:
- make invalid DT data parsing fatal (Suggested by Guenter Roeck)
- add more variant checks and return errors
- remove inline from _si5351_* functions
- remove pll reset/clkout powerdown for gapless tuning
  (Provided by Michal Backraty)

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1510 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 ++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1904 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator@60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..8927284
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1510 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return 0;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C &&
+	    parent != SI5351_PLL_SRC_XTAL)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+	return 0;
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return 0;
+
+	if (num > 8)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+	return 0;
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+			       SI5351_MULTISYNTH_SRC_VCO1);
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+	return 0;
+}
+
+static int _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+	return 0;
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			return -EINVAL;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p) {
+			dev_err(&client->dev,
+				"missing pll-source for pll %d\n", num);
+			return -EINVAL;
+		}
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			if (pdata->variant != SI5351_VARIANT_C) {
+				dev_err(&client->dev,
+					"invalid parent %d for pll %d\n",
+					val, num);
+				return -EINVAL;
+			}
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_err(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			return -EINVAL;
+		}
+	}
+
+	/* per clkout properties */
+	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 >= 8 ||
+		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
+			dev_err(&client->dev, "invalid clkout %d\n", num);
+			return -EINVAL;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for multisynth %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				if (pdata->variant != SI5351_VARIANT_C) {
+					dev_err(&client->dev,
+						"invalid parent %d for clkout %d\n",
+						val, num);
+					return -EINVAL;
+				}
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid drive strength %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++) {
+		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent pll %d to %d\n",
+				n, pdata->pll_src[n]);
+			return ret;
+		}
+	}
+
+	for (n = 0; n < 8; n++) {
+		ret = _si5351_msynth_reparent(drvdata, n,
+					      pdata->clkout[n].multisynth_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent multisynth %d to %d\n",
+				n, pdata->clkout[n].multisynth_src);
+			return ret;
+		}
+
+		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
+							pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive strength of clkout%d to %d\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..92dabca
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v8] clk: add si5351 i2c common clock driver
@ 2013-04-11 19:42             ` Sebastian Hesselbarth
  0 siblings, 0 replies; 95+ messages in thread
From: Sebastian Hesselbarth @ 2013-04-11 19:42 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a common clock driver for Silicon Labs Si5351a/b/c
i2c programmable clock generators. Currently, the driver does not
support VXCO feature of si5351b. Passing platform_data or DT bindings
selectively allows to overwrite stored Si5351 configuration which is
very helpful for clock generators with empty eeprom configuration.
Corresponding device tree binding documentation is also added.

Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Tested-by: Daniel Mack <zonque@gmail.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
---
Changes from v7:
- readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)

Changes from v6:
- make invalid DT data parsing fatal (Suggested by Guenter Roeck)
- add more variant checks and return errors
- remove inline from _si5351_* functions
- remove pll reset/clkout powerdown for gapless tuning
  (Provided by Michal Backraty)

Changes from v5:
- removed __clk_set_flags
- remove CONFIG_OF dependency
- introduce si5351_platform_data (non-DT untested)
- parse DT into si5351_platform_data (tested)
- minor cleanups

Changes from v4:
- move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
- use __clk_set_flags() helper

Changes from v3:
- add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
- use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
- check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
- clean up i2c client init (Reported by Lars-Peter Clausen)
- silence successful probe (Suggested by Lars-Peter Clausen)
- make CONFIG_CLK_SI5351 depend on CONFIG_OF

Changes from v2:
- add curly brackets to if-else-statements (Reported by Daniel Mack)
- fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
- fix parameter address calculation for clk6/clk7

Changes from v1:
- remove .is_enabled functions as they read from i2c
  (Reported by Daniel Mack)
- add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
  its own multisync

Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Rob Landley <rob@landley.net>
Cc: Mike Turquette <mturquette@linaro.org>
Cc: Stephen Warren <swarren@nvidia.com>
Cc: Thierry Reding <thierry.reding@avionic-design.de>
Cc: Dom Cobley <popcornmix@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Rabeeh Khoury <rabeeh@solid-run.com>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: Lars-Peter Clausen <lars@metafoo.de>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
Cc: devicetree-discuss at lists.ozlabs.org
Cc: linux-doc at vger.kernel.org
Cc: linux-kernel at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
---
 .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/clk/Kconfig                                |    9 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5351.c                           | 1510 ++++++++++++++++++++
 drivers/clk/clk-si5351.h                           |  155 ++
 include/linux/platform_data/si5351.h               |  114 ++
 7 files changed, 1904 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
 create mode 100644 drivers/clk/clk-si5351.c
 create mode 100644 drivers/clk/clk-si5351.h
 create mode 100644 include/linux/platform_data/si5351.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
new file mode 100644
index 0000000..cc37465
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
@@ -0,0 +1,114 @@
+Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
+
+Reference
+[1] Si5351A/B/C Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+
+The Si5351a/b/c are programmable i2c clock generators with upto 8 output
+clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
+3 output clocks are accessible. The internal structure of the clock
+generators can be found in [1].
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
+- reg: i2c device address, shall be 0x60 or 0x61.
+- #clock-cells: from common clock binding; shall be set to 1.
+- clocks: from common clock binding; list of parent clock
+  handles, shall be xtal reference clock or xtal and clkin for
+  si5351c only.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties:
+- silabs,pll-source: pair of (number, source) for each pll. Allows
+  to overwrite clock source of pll A (number=0) or B (number=1).
+
+==Child nodes==
+
+Each of the clock outputs can be overwritten individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, the eeprom configuration is not overwritten.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,clock-source: source clock of the output divider stage N, shall be
+  0 = multisynth N
+  1 = multisynth 0 for output clocks 0-3, else multisynth4
+  2 = xtal
+  3 = clkin (si5351c only)
+- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
+- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
+  divider.
+- silabs,pll-master: boolean, multisynth can change pll frequency.
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+
+i2c-master-node {
+
+	/* Si5351a msop10 i2c clock generator */
+	si5351a: clock-generator at 60 {
+		compatible = "silabs,si5351a-msop";
+		reg = <0x60>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal input to 25MHz reference */
+		clocks = <&ref25>;
+
+		/* connect xtal input as source of pll0 and pll1 */
+		silabs,pll-source = <0 0>, <1 0>;
+
+		/*
+		 * overwrite clkout0 configuration with:
+		 * - 8mA output drive strength
+		 * - pll0 as clock source of multisynth0
+		 * - multisynth0 as clock source of output divider
+		 * - multisynth0 can change pll0
+		 * - set initial clock frequency of 74.25MHz
+		 */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-strength = <8>;
+			silabs,multisynth-source = <0>;
+			silabs,clock-source = <0>;
+			silabs,pll-master;
+			clock-frequency = <74250000>;
+		};
+
+		/*
+		 * overwrite clkout1 configuration with:
+		 * - 4mA output drive strength
+		 * - pll1 as clock source of multisynth1
+		 * - multisynth1 as clock source of output divider
+		 * - multisynth1 can change pll1
+		 */
+		clkout1 {
+			reg = <1>;
+			silabs,drive-strength = <4>;
+			silabs,multisynth-source = <1>;
+			silabs,clock-source = <0>;
+			pll-master;
+		};
+
+		/*
+		 * overwrite clkout2 configuration with:
+		 * - xtal as clock source of output divider
+		 */
+		clkout2 {
+			reg = <2>;
+			silabs,clock-source = <2>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 19e1ef7..ca60849 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
 sbs	Smart Battery System
 schindler	Schindler
 sil	Silicon Image
+silabs	Silicon Laboratories
 simtek
 sirf	SiRF Technology, Inc.
 snps 	Synopsys, Inc.
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a47e6ee..5039e41 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
 	---help---
 	  This driver supports Maxim 77686 crystal oscillator clock. 
 
+config COMMON_CLK_SI5351
+	tristate "Clock driver for SiLabs 5351A/B/C"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5351A/B/C programmable clock
+	  generators.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 300d477..92ca698 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
+obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
new file mode 100644
index 0000000..8927284
--- /dev/null
+++ b/drivers/clk/clk-si5351.c
@@ -0,0 +1,1510 @@
+/*
+ * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * References:
+ * [1] "Si5351A/B/C Data Sheet"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
+ * [2] "Manually Generating an Si5351 Register Map"
+ *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/rational.h>
+#include <linux/i2c.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5351.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <asm/div64.h>
+
+#include "clk-si5351.h"
+
+struct si5351_driver_data;
+
+struct si5351_parameters {
+	unsigned long	p1;
+	unsigned long	p2;
+	unsigned long	p3;
+	int		valid;
+};
+
+struct si5351_hw_data {
+	struct clk_hw			hw;
+	struct si5351_driver_data	*drvdata;
+	struct si5351_parameters	params;
+	unsigned char			num;
+};
+
+struct si5351_driver_data {
+	enum si5351_variant	variant;
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk_hw		xtal;
+	struct clk		*pclkin;
+	const char		*pclkin_name;
+	struct clk_hw		clkin;
+
+	struct si5351_hw_data	pll[2];
+	struct si5351_hw_data	*msynth;
+	struct si5351_hw_data	*clkout;
+};
+
+static const char const *si5351_input_names[] = {
+	"xtal", "clkin"
+};
+static const char const *si5351_pll_names[] = {
+	"plla", "pllb", "vxco"
+};
+static const char const *si5351_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
+};
+static const char const *si5351_clkout_names[] = {
+	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
+};
+
+/*
+ * Si5351 i2c regmap
+ */
+static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 count, u8 *buf)
+{
+	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
+				   u8 reg, u8 val)
+{
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
+				    u8 reg, u8 count, const u8 *buf)
+{
+	return regmap_raw_write(drvdata->regmap, reg, buf, count);
+}
+
+static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
+				  u8 reg, u8 mask, u8 val)
+{
+	return regmap_update_bits(drvdata->regmap, reg, mask, val);
+}
+
+static inline u8 si5351_msynth_params_address(int num)
+{
+	if (num > 5)
+		return SI5351_CLK6_PARAMETERS + (num - 6);
+	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
+}
+
+static void si5351_read_parameters(struct si5351_driver_data *drvdata,
+				   u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = si5351_reg_read(drvdata, reg);
+		params->p1 = buf[0];
+		params->p2 = 0;
+		params->p3 = 1;
+		break;
+	default:
+		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
+		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
+		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
+	}
+	params->valid = 1;
+}
+
+static void si5351_write_parameters(struct si5351_driver_data *drvdata,
+				    u8 reg, struct si5351_parameters *params)
+{
+	u8 buf[SI5351_PARAMETERS_LENGTH];
+
+	switch (reg) {
+	case SI5351_CLK6_PARAMETERS:
+	case SI5351_CLK7_PARAMETERS:
+		buf[0] = params->p1 & 0xff;
+		si5351_reg_write(drvdata, reg, buf[0]);
+		break;
+	default:
+		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
+		buf[1] = params->p3 & 0xff;
+		/* save rdiv and divby4 */
+		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
+		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
+		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
+		buf[4] = params->p1 & 0xff;
+		buf[5] = ((params->p3 & 0xf0000) >> 12) |
+			((params->p2 & 0xf0000) >> 16);
+		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
+		buf[7] = params->p2 & 0xff;
+		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
+	}
+}
+
+static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SI5351_DEVICE_STATUS:
+	case SI5351_INTERRUPT_STATUS:
+	case SI5351_PLL_RESET:
+		return true;
+	}
+	return false;
+}
+
+static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	/* reserved registers */
+	if (reg >= 4 && reg <= 8)
+		return false;
+	if (reg >= 10 && reg <= 14)
+		return false;
+	if (reg >= 173 && reg <= 176)
+		return false;
+	if (reg >= 178 && reg <= 182)
+		return false;
+	/* read-only */
+	if (reg == SI5351_DEVICE_STATUS)
+		return false;
+	return true;
+}
+
+static struct regmap_config si5351_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 187,
+	.writeable_reg = si5351_regmap_is_writeable,
+	.volatile_reg = si5351_regmap_is_volatile,
+};
+
+/*
+ * Si5351 xtal clock input
+ */
+static int si5351_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
+	return 0;
+}
+
+static void si5351_xtal_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, xtal);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_XTAL_ENABLE, 0);
+}
+
+static const struct clk_ops si5351_xtal_ops = {
+	.prepare = si5351_xtal_prepare,
+	.unprepare = si5351_xtal_unprepare,
+};
+
+/*
+ * Si5351 clkin clock input (Si5351C only)
+ */
+static int si5351_clkin_prepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
+	return 0;
+}
+
+static void si5351_clkin_unprepare(struct clk_hw *hw)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
+			SI5351_CLKIN_ENABLE, 0);
+}
+
+/*
+ * CMOS clock source constraints:
+ * The input frequency range of the PLL is 10Mhz to 40MHz.
+ * If CLKIN is >40MHz, the input divider must be used.
+ */
+static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct si5351_driver_data *drvdata =
+		container_of(hw, struct si5351_driver_data, clkin);
+	unsigned long rate;
+	unsigned char idiv;
+
+	rate = parent_rate;
+	if (parent_rate > 160000000) {
+		idiv = SI5351_CLKIN_DIV_8;
+		rate /= 8;
+	} else if (parent_rate > 80000000) {
+		idiv = SI5351_CLKIN_DIV_4;
+		rate /= 4;
+	} else if (parent_rate > 40000000) {
+		idiv = SI5351_CLKIN_DIV_2;
+		rate /= 2;
+	} else {
+		idiv = SI5351_CLKIN_DIV_1;
+	}
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+			SI5351_CLKIN_DIV_MASK, idiv);
+
+	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
+		__func__, (1 << (idiv >> 6)), rate);
+
+	return rate;
+}
+
+static const struct clk_ops si5351_clkin_ops = {
+	.prepare = si5351_clkin_prepare,
+	.unprepare = si5351_clkin_unprepare,
+	.recalc_rate = si5351_clkin_recalc_rate,
+};
+
+/*
+ * Si5351 vxco clock input (Si5351B only)
+ */
+
+static int si5351_vxco_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
+
+	return 0;
+}
+
+static void si5351_vxco_unprepare(struct clk_hw *hw)
+{
+}
+
+static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	return 0;
+}
+
+static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent)
+{
+	return 0;
+}
+
+static const struct clk_ops si5351_vxco_ops = {
+	.prepare = si5351_vxco_prepare,
+	.unprepare = si5351_vxco_unprepare,
+	.recalc_rate = si5351_vxco_recalc_rate,
+	.set_rate = si5351_vxco_set_rate,
+};
+
+/*
+ * Si5351 pll a/b
+ *
+ * Feedback Multisynth Divider Equations [2]
+ *
+ * fVCO = fIN * (a + b/c)
+ *
+ * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
+ * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
+ *
+ * Feedback Multisynth Register Equations
+ *
+ * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * (3) MSNx_P3[19:0] = c
+ *
+ * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
+ *
+ * Using (4) on (1) yields:
+ * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
+ * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
+ *
+ * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
+ *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
+ *
+ */
+static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
+				int num, enum si5351_pll_src parent)
+{
+	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+
+	if (parent == SI5351_PLL_SRC_DEFAULT)
+		return 0;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (drvdata->variant != SI5351_VARIANT_C &&
+	    parent != SI5351_PLL_SRC_XTAL)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
+			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
+	return 0;
+}
+
+static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
+
+	return (val & mask) ? 1 : 0;
+}
+
+static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
+	    index > 0)
+		return -EPERM;
+
+	if (index > 1)
+		return -EINVAL;
+
+	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
+			     (index == 0) ? SI5351_PLL_SRC_XTAL :
+			     SI5351_PLL_SRC_CLKIN);
+}
+
+static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+	unsigned long long rate;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
+	rate  = hwdata->params.p1 * hwdata->params.p3;
+	rate += 512 * hwdata->params.p3;
+	rate += hwdata->params.p2;
+	rate *= parent_rate;
+	do_div(rate, 128 * hwdata->params.p3);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long rfrac, denom, a, b, c;
+	unsigned long long lltmp;
+
+	if (rate < SI5351_PLL_VCO_MIN)
+		rate = SI5351_PLL_VCO_MIN;
+	if (rate > SI5351_PLL_VCO_MAX)
+		rate = SI5351_PLL_VCO_MAX;
+
+	/* determine integer part of feedback equation */
+	a = rate / *parent_rate;
+
+	if (a < SI5351_PLL_A_MIN)
+		rate = *parent_rate * SI5351_PLL_A_MIN;
+	if (a > SI5351_PLL_A_MAX)
+		rate = *parent_rate * SI5351_PLL_A_MAX;
+
+	/* find best approximation for b/c = fVCO mod fIN */
+	denom = 1000 * 1000;
+	lltmp = rate % (*parent_rate);
+	lltmp *= denom;
+	do_div(lltmp, *parent_rate);
+	rfrac = (unsigned long)lltmp;
+
+	b = 0;
+	c = 1;
+	if (rfrac)
+		rational_best_approximation(rfrac, denom,
+				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
+
+	/* calculate parameters */
+	hwdata->params.p3  = c;
+	hwdata->params.p2  = (128 * b) % c;
+	hwdata->params.p1  = 128 * a;
+	hwdata->params.p1 += (128 * b / c);
+	hwdata->params.p1 -= 512;
+
+	/* recalculate rate by fIN * (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= b;
+	do_div(lltmp, c);
+
+	rate  = (unsigned long)lltmp;
+	rate += *parent_rate * a;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
+		SI5351_PLLB_PARAMETERS;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
+		SI5351_CLK_INTEGER_MODE,
+		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_pll_ops = {
+	.set_parent = si5351_pll_set_parent,
+	.get_parent = si5351_pll_get_parent,
+	.recalc_rate = si5351_pll_recalc_rate,
+	.round_rate = si5351_pll_round_rate,
+	.set_rate = si5351_pll_set_rate,
+};
+
+/*
+ * Si5351 multisync divider
+ *
+ * for fOUT <= 150 MHz:
+ *
+ * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
+ *
+ * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
+ * fIN = fVCO0, fVCO1
+ *
+ * Output Clock Multisynth Register Equations
+ *
+ * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
+ * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
+ * MSx_P3[19:0] = c
+ *
+ * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
+ *
+ * for 150MHz < fOUT <= 160MHz:
+ *
+ * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
+ */
+static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_multisynth_src parent)
+{
+	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
+		return 0;
+
+	if (num > 8)
+		return -EINVAL;
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
+			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
+			SI5351_CLK_PLL_SELECT);
+	return 0;
+}
+
+static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+
+	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
+}
+
+static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
+			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
+			       SI5351_MULTISYNTH_SRC_VCO1);
+}
+
+static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	unsigned long long rate;
+	unsigned long m;
+
+	if (!hwdata->params.valid)
+		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (hwdata->params.p3 == 0)
+		return parent_rate;
+
+	/*
+	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
+	 * multisync6-7: fOUT = fIN / P1
+	 */
+	rate = parent_rate;
+	if (hwdata->num > 5) {
+		m = hwdata->params.p1;
+	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
+		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
+		m = 4;
+	} else {
+		rate *= 128 * hwdata->params.p3;
+		m = hwdata->params.p1 * hwdata->params.p3;
+		m += hwdata->params.p2;
+		m += 512 * hwdata->params.p3;
+	}
+
+	if (m == 0)
+		return 0;
+	do_div(rate, m);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		m, parent_rate, (unsigned long)rate);
+
+	return (unsigned long)rate;
+}
+
+static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long long lltmp;
+	unsigned long a, b, c;
+	int divby4;
+
+	/* multisync6-7 can only handle freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
+		rate = SI5351_MULTISYNTH67_MAX_FREQ;
+
+	/* multisync frequency is 1MHz .. 160MHz */
+	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
+		rate = SI5351_MULTISYNTH_MAX_FREQ;
+	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
+		rate = SI5351_MULTISYNTH_MIN_FREQ;
+
+	divby4 = 0;
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* multisync can set pll */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/*
+		 * find largest integer divider for max
+		 * vco frequency and given target rate
+		 */
+		if (divby4 == 0) {
+			lltmp = SI5351_PLL_VCO_MAX;
+			do_div(lltmp, rate);
+			a = (unsigned long)lltmp;
+		} else
+			a = 4;
+
+		b = 0;
+		c = 1;
+
+		*parent_rate = a * rate;
+	} else {
+		unsigned long rfrac, denom;
+
+		/* disable divby4 */
+		if (divby4) {
+			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
+			divby4 = 0;
+		}
+
+		/* determine integer part of divider equation */
+		a = *parent_rate / rate;
+		if (a < SI5351_MULTISYNTH_A_MIN)
+			a = SI5351_MULTISYNTH_A_MIN;
+		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
+			a = SI5351_MULTISYNTH67_A_MAX;
+		else if (a > SI5351_MULTISYNTH_A_MAX)
+			a = SI5351_MULTISYNTH_A_MAX;
+
+		/* find best approximation for b/c = fVCO mod fOUT */
+		denom = 1000 * 1000;
+		lltmp = (*parent_rate) % rate;
+		lltmp *= denom;
+		do_div(lltmp, rate);
+		rfrac = (unsigned long)lltmp;
+
+		b = 0;
+		c = 1;
+		if (rfrac)
+			rational_best_approximation(rfrac, denom,
+			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
+			    &b, &c);
+	}
+
+	/* recalculate rate by fOUT = fIN / (a + b/c) */
+	lltmp  = *parent_rate;
+	lltmp *= c;
+	do_div(lltmp, a * c + b);
+	rate  = (unsigned long)lltmp;
+
+	/* calculate parameters */
+	if (divby4) {
+		hwdata->params.p3 = 1;
+		hwdata->params.p2 = 0;
+		hwdata->params.p1 = 0;
+	} else {
+		hwdata->params.p3  = c;
+		hwdata->params.p2  = (128 * b) % c;
+		hwdata->params.p1  = 128 * a;
+		hwdata->params.p1 += (128 * b / c);
+		hwdata->params.p1 -= 512;
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	u8 reg = si5351_msynth_params_address(hwdata->num);
+	int divby4 = 0;
+
+	/* write multisynth parameters */
+	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
+
+	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
+		divby4 = 1;
+
+	/* enable/disable integer mode and divby4 on multisynth0-5 */
+	if (hwdata->num < 6) {
+		si5351_set_bits(hwdata->drvdata, reg + 2,
+				SI5351_OUTPUT_CLK_DIVBY4,
+				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_INTEGER_MODE,
+			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
+	}
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk),
+		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
+		divby4, parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_msynth_ops = {
+	.set_parent = si5351_msynth_set_parent,
+	.get_parent = si5351_msynth_get_parent,
+	.recalc_rate = si5351_msynth_recalc_rate,
+	.round_rate = si5351_msynth_round_rate,
+	.set_rate = si5351_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout divider
+ */
+static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
+				   int num, enum si5351_clkout_src parent)
+{
+	u8 val;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (parent) {
+	case SI5351_CLKOUT_SRC_MSYNTH_N:
+		val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		break;
+	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
+		/* clk0/clk4 can only connect to its own multisync */
+		if (num == 0 || num == 4)
+			val = SI5351_CLK_INPUT_MULTISYNTH_N;
+		else
+			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
+		break;
+	case SI5351_CLKOUT_SRC_XTAL:
+		val = SI5351_CLK_INPUT_XTAL;
+		break;
+	case SI5351_CLKOUT_SRC_CLKIN:
+		if (drvdata->variant != SI5351_VARIANT_C)
+			return -EINVAL;
+
+		val = SI5351_CLK_INPUT_CLKIN;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_INPUT_MASK, val);
+	return 0;
+}
+
+static int _si5351_clkout_set_drive_strength(
+	struct si5351_driver_data *drvdata, int num,
+	enum si5351_drive_strength drive)
+{
+	u8 mask;
+
+	if (num > 8)
+		return -EINVAL;
+
+	switch (drive) {
+	case SI5351_DRIVE_2MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
+		break;
+	case SI5351_DRIVE_4MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
+		break;
+	case SI5351_DRIVE_6MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
+		break;
+	case SI5351_DRIVE_8MA:
+		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
+		break;
+	default:
+		return 0;
+	}
+
+	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
+			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
+	return 0;
+}
+
+static int si5351_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), 0);
+	return 0;
+}
+
+static void si5351_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
+	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
+			(1 << hwdata->num), (1 << hwdata->num));
+}
+
+static u8 si5351_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	int index = 0;
+	unsigned char val;
+
+	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
+	switch (val & SI5351_CLK_INPUT_MASK) {
+	case SI5351_CLK_INPUT_MULTISYNTH_N:
+		index = 0;
+		break;
+	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
+		index = 1;
+		break;
+	case SI5351_CLK_INPUT_XTAL:
+		index = 2;
+		break;
+	case SI5351_CLK_INPUT_CLKIN:
+		index = 3;
+		break;
+	}
+
+	return index;
+}
+
+static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
+
+	switch (index) {
+	case 0:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
+		break;
+	case 1:
+		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
+		break;
+	case 2:
+		parent = SI5351_CLKOUT_SRC_XTAL;
+		break;
+	case 3:
+		parent = SI5351_CLKOUT_SRC_CLKIN;
+		break;
+	}
+
+	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
+}
+
+static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char reg;
+	unsigned char rdiv;
+
+	if (hwdata->num > 5)
+		reg = si5351_msynth_params_address(hwdata->num) + 2;
+	else
+		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
+
+	rdiv = si5351_reg_read(hwdata->drvdata, reg);
+	if (hwdata->num == 6) {
+		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
+	} else {
+		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
+		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
+	}
+
+	return parent_rate >> rdiv;
+}
+
+static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned char rdiv;
+
+	/* clkout6/7 can only handle output freqencies < 150MHz */
+	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
+		rate = SI5351_CLKOUT67_MAX_FREQ;
+
+	/* clkout freqency is 8kHz - 160MHz */
+	if (rate > SI5351_CLKOUT_MAX_FREQ)
+		rate = SI5351_CLKOUT_MAX_FREQ;
+	if (rate < SI5351_CLKOUT_MIN_FREQ)
+		rate = SI5351_CLKOUT_MIN_FREQ;
+
+	/* request frequency if multisync master */
+	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
+		/* use r divider for frequencies below 1MHz */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
+		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
+			rdiv += 1;
+			rate *= 2;
+		}
+		*parent_rate = rate;
+	} else {
+		unsigned long new_rate, new_err, err;
+
+		/* round to closed rdiv */
+		rdiv = SI5351_OUTPUT_CLK_DIV_1;
+		new_rate = *parent_rate;
+		err = abs(new_rate - rate);
+		do {
+			new_rate >>= 1;
+			new_err = abs(new_rate - rate);
+			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+				break;
+			rdiv++;
+			err = new_err;
+		} while (1);
+	}
+	rate = *parent_rate >> rdiv;
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		*parent_rate, rate);
+
+	return rate;
+}
+
+static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5351_hw_data *hwdata =
+		container_of(hw, struct si5351_hw_data, hw);
+	unsigned long new_rate, new_err, err;
+	unsigned char rdiv;
+
+	/* round to closed rdiv */
+	rdiv = SI5351_OUTPUT_CLK_DIV_1;
+	new_rate = parent_rate;
+	err = abs(new_rate - rate);
+	do {
+		new_rate >>= 1;
+		new_err = abs(new_rate - rate);
+		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
+			break;
+		rdiv++;
+		err = new_err;
+	} while (1);
+
+	/* write output divider */
+	switch (hwdata->num) {
+	case 6:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
+		break;
+	case 7:
+		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+		break;
+	default:
+		si5351_set_bits(hwdata->drvdata,
+				si5351_msynth_params_address(hwdata->num) + 2,
+				SI5351_OUTPUT_CLK_DIV_MASK,
+				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
+	}
+
+	/* powerup clkout */
+	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
+			SI5351_CLK_POWERDOWN, 0);
+
+	dev_dbg(&hwdata->drvdata->client->dev,
+		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
+		parent_rate, rate);
+
+	return 0;
+}
+
+static const struct clk_ops si5351_clkout_ops = {
+	.prepare = si5351_clkout_prepare,
+	.unprepare = si5351_clkout_unprepare,
+	.set_parent = si5351_clkout_set_parent,
+	.get_parent = si5351_clkout_get_parent,
+	.recalc_rate = si5351_clkout_recalc_rate,
+	.round_rate = si5351_clkout_round_rate,
+	.set_rate = si5351_clkout_set_rate,
+};
+
+/*
+ * Si5351 i2c probe and DT
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5351_dt_ids[] = {
+	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
+	{ .compatible = "silabs,si5351a-msop",
+					 .data = (void *)SI5351_VARIANT_A3, },
+	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
+	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5351_dt_ids);
+
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5351_platform_data *pdata;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *p;
+	int num = 0;
+	u32 val;
+
+	if (np == NULL)
+		return 0;
+
+	match = of_match_node(si5351_dt_ids, np);
+	if (match == NULL)
+		return -EINVAL;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->variant = (enum si5351_variant)match->data;
+	pdata->clk_xtal = of_clk_get(np, 0);
+	if (!IS_ERR(pdata->clk_xtal))
+		clk_put(pdata->clk_xtal);
+	pdata->clk_clkin = of_clk_get(np, 1);
+	if (!IS_ERR(pdata->clk_clkin))
+		clk_put(pdata->clk_clkin);
+
+	/*
+	 * property silabs,pll-source : <num src>, [<..>]
+	 * allow to selectively set pll source
+	 */
+	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
+		if (num >= 2) {
+			dev_err(&client->dev,
+				"invalid pll %d on pll-source prop\n", num);
+			return -EINVAL;
+		}
+
+		p = of_prop_next_u32(prop, p, &val);
+		if (!p) {
+			dev_err(&client->dev,
+				"missing pll-source for pll %d\n", num);
+			return -EINVAL;
+		}
+
+		switch (val) {
+		case 0:
+			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
+			break;
+		case 1:
+			if (pdata->variant != SI5351_VARIANT_C) {
+				dev_err(&client->dev,
+					"invalid parent %d for pll %d\n",
+					val, num);
+				return -EINVAL;
+			}
+			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
+			break;
+		default:
+			dev_err(&client->dev,
+				 "invalid parent %d for pll %d\n", val, num);
+			return -EINVAL;
+		}
+	}
+
+	/* per clkout properties */
+	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 >= 8 ||
+		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
+			dev_err(&client->dev, "invalid clkout %d\n", num);
+			return -EINVAL;
+		}
+
+		if (!of_property_read_u32(child, "silabs,multisynth-source",
+					  &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO0;
+				break;
+			case 1:
+				pdata->clkout[num].multisynth_src =
+					SI5351_MULTISYNTH_SRC_VCO1;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for multisynth %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case 0:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_N;
+				break;
+			case 1:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_MSYNTH_0_4;
+				break;
+			case 2:
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_XTAL;
+				break;
+			case 3:
+				if (pdata->variant != SI5351_VARIANT_C) {
+					dev_err(&client->dev,
+						"invalid parent %d for clkout %d\n",
+						val, num);
+					return -EINVAL;
+				}
+				pdata->clkout[num].clkout_src =
+					SI5351_CLKOUT_SRC_CLKIN;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid parent %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "silabs,drive-strength",
+					  &val)) {
+			switch (val) {
+			case SI5351_DRIVE_2MA:
+			case SI5351_DRIVE_4MA:
+			case SI5351_DRIVE_6MA:
+			case SI5351_DRIVE_8MA:
+				pdata->clkout[num].drive = val;
+				break;
+			default:
+				dev_err(&client->dev,
+					"invalid drive strength %d for clkout %d\n",
+					val, num);
+				return -EINVAL;
+			}
+		}
+
+		if (!of_property_read_u32(child, "clock-frequency", &val))
+			pdata->clkout[num].rate = val;
+
+		pdata->clkout[num].pll_master =
+			of_property_read_bool(child, "silabs,pll-master");
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5351_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static int si5351_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5351_platform_data *pdata;
+	struct si5351_driver_data *drvdata;
+	struct clk_init_data init;
+	struct clk *clk;
+	const char *parent_names[4];
+	u8 num_parents, num_clocks;
+	int ret, n;
+
+	ret = si5351_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) {
+		dev_err(&client->dev, "unable to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->variant = pdata->variant;
+	drvdata->pxtal = pdata->clk_xtal;
+	drvdata->pclkin = pdata->clk_clkin;
+
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	/* Disable interrupts */
+	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
+	/* Set disabled output drivers to drive low */
+	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
+	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
+	/* Ensure pll select is on XTAL for Si5351A/B */
+	if (drvdata->variant != SI5351_VARIANT_C)
+		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
+				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
+
+	/* setup clock configuration */
+	for (n = 0; n < 2; n++) {
+		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent pll %d to %d\n",
+				n, pdata->pll_src[n]);
+			return ret;
+		}
+	}
+
+	for (n = 0; n < 8; n++) {
+		ret = _si5351_msynth_reparent(drvdata, n,
+					      pdata->clkout[n].multisynth_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent multisynth %d to %d\n",
+				n, pdata->clkout[n].multisynth_src);
+			return ret;
+		}
+
+		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
+							pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive strength of clkout%d to %d\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+	}
+
+	/* register xtal input clock gate */
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_input_names[0];
+	init.ops = &si5351_xtal_ops;
+	init.flags = 0;
+	if (!IS_ERR(drvdata->pxtal)) {
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		init.parent_names = &drvdata->pxtal_name;
+		init.num_parents = 1;
+	}
+	drvdata->xtal.init = &init;
+	clk = devm_clk_register(&client->dev, &drvdata->xtal);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return PTR_ERR(clk);
+	}
+
+	/* register clkin input clock gate */
+	if (drvdata->variant == SI5351_VARIANT_C) {
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_input_names[1];
+		init.ops = &si5351_clkin_ops;
+		if (!IS_ERR(drvdata->pclkin)) {
+			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
+			init.parent_names = &drvdata->pclkin_name;
+			init.num_parents = 1;
+		}
+		drvdata->clkin.init = &init;
+		clk = devm_clk_register(&client->dev, &drvdata->clkin);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return PTR_ERR(clk);
+		}
+	}
+
+	/* Si5351C allows to mux either xtal or clkin to PLL input */
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
+	parent_names[0] = si5351_input_names[0];
+	parent_names[1] = si5351_input_names[1];
+
+	/* register PLLA */
+	drvdata->pll[0].num = 0;
+	drvdata->pll[0].drvdata = drvdata;
+	drvdata->pll[0].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	init.name = si5351_pll_names[0];
+	init.ops = &si5351_pll_ops;
+	init.flags = 0;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register PLLB or VXCO (Si5351B) */
+	drvdata->pll[1].num = 1;
+	drvdata->pll[1].drvdata = drvdata;
+	drvdata->pll[1].hw.init = &init;
+	memset(&init, 0, sizeof(init));
+	if (drvdata->variant == SI5351_VARIANT_B) {
+		init.name = si5351_pll_names[2];
+		init.ops = &si5351_vxco_ops;
+		init.flags = CLK_IS_ROOT;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+	} else {
+		init.name = si5351_pll_names[1];
+		init.ops = &si5351_pll_ops;
+		init.flags = 0;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+	}
+	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
+	if (IS_ERR(clk)) {
+		dev_err(&client->dev, "unable to register %s\n", init.name);
+		return -EINVAL;
+	}
+
+	/* register clk multisync and clk out divider */
+	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
+	parent_names[0] = si5351_pll_names[0];
+	if (drvdata->variant == SI5351_VARIANT_B)
+		parent_names[1] = si5351_pll_names[2];
+	else
+		parent_names[1] = si5351_pll_names[1];
+
+	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = num_clocks;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks))
+		return -ENOMEM;
+
+	for (n = 0; n < num_clocks; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		drvdata->msynth[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_msynth_names[n];
+		init.ops = &si5351_msynth_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].pll_master)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = 2;
+		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+	}
+
+	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
+	parent_names[2] = si5351_input_names[0];
+	parent_names[3] = si5351_input_names[1];
+	for (n = 0; n < num_clocks; n++) {
+		parent_names[0] = si5351_msynth_names[n];
+		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
+			si5351_msynth_names[4];
+
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		drvdata->clkout[n].hw.init = &init;
+		memset(&init, 0, sizeof(init));
+		init.name = si5351_clkout_names[n];
+		init.ops = &si5351_clkout_ops;
+		init.flags = 0;
+		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
+			init.flags |= CLK_SET_RATE_PARENT;
+		init.parent_names = parent_names;
+		init.num_parents = num_parents;
+		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
+		if (IS_ERR(clk)) {
+			dev_err(&client->dev, "unable to register %s\n",
+				init.name);
+			return -EINVAL;
+		}
+		drvdata->onecell.clks[n] = clk;
+	}
+
+	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;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id si5351_i2c_ids[] = {
+	{ "silabs,si5351", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
+
+static struct i2c_driver si5351_driver = {
+	.driver = {
+		.name = "si5351",
+		.of_match_table = of_match_ptr(si5351_dt_ids),
+	},
+	.probe = si5351_i2c_probe,
+	.id_table = si5351_i2c_ids,
+};
+module_i2c_driver(si5351_driver);
+
+MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth at gmail.com");
+MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
new file mode 100644
index 0000000..af41b50
--- /dev/null
+++ b/drivers/clk/clk-si5351.h
@@ -0,0 +1,155 @@
+/*
+ * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Rabeeh Khoury <rabeeh@solid-run.com>
+ *
+ * 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_SI5351_H_
+#define _CLK_SI5351_H_
+
+#define SI5351_BUS_BASE_ADDR			0x60
+
+#define SI5351_PLL_VCO_MIN			600000000
+#define SI5351_PLL_VCO_MAX			900000000
+#define SI5351_MULTISYNTH_MIN_FREQ		1000000
+#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
+#define SI5351_MULTISYNTH_MAX_FREQ		160000000
+#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
+#define SI5351_CLKOUT_MIN_FREQ			8000
+#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
+#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
+
+#define SI5351_PLL_A_MIN			15
+#define SI5351_PLL_A_MAX			90
+#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
+#define SI5351_PLL_C_MAX			1048575
+#define SI5351_MULTISYNTH_A_MIN			6
+#define SI5351_MULTISYNTH_A_MAX			1800
+#define SI5351_MULTISYNTH67_A_MAX		254
+#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
+#define SI5351_MULTISYNTH_C_MAX			1048575
+#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
+#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
+#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
+
+#define SI5351_DEVICE_STATUS			0
+#define SI5351_INTERRUPT_STATUS			1
+#define SI5351_INTERRUPT_MASK			2
+#define  SI5351_STATUS_SYS_INIT			(1<<7)
+#define  SI5351_STATUS_LOL_B			(1<<6)
+#define  SI5351_STATUS_LOL_A			(1<<5)
+#define  SI5351_STATUS_LOS			(1<<4)
+#define SI5351_OUTPUT_ENABLE_CTRL		3
+#define SI5351_OEB_PIN_ENABLE_CTRL		9
+#define SI5351_PLL_INPUT_SOURCE			15
+#define  SI5351_CLKIN_DIV_MASK			(3<<6)
+#define  SI5351_CLKIN_DIV_1			(0<<6)
+#define  SI5351_CLKIN_DIV_2			(1<<6)
+#define  SI5351_CLKIN_DIV_4			(2<<6)
+#define  SI5351_CLKIN_DIV_8			(3<<6)
+#define  SI5351_PLLB_SOURCE			(1<<3)
+#define  SI5351_PLLA_SOURCE			(1<<2)
+
+#define SI5351_CLK0_CTRL			16
+#define SI5351_CLK1_CTRL			17
+#define SI5351_CLK2_CTRL			18
+#define SI5351_CLK3_CTRL			19
+#define SI5351_CLK4_CTRL			20
+#define SI5351_CLK5_CTRL			21
+#define SI5351_CLK6_CTRL			22
+#define SI5351_CLK7_CTRL			23
+#define  SI5351_CLK_POWERDOWN			(1<<7)
+#define  SI5351_CLK_INTEGER_MODE		(1<<6)
+#define  SI5351_CLK_PLL_SELECT			(1<<5)
+#define  SI5351_CLK_INVERT			(1<<4)
+#define  SI5351_CLK_INPUT_MASK			(3<<2)
+#define  SI5351_CLK_INPUT_XTAL			(0<<2)
+#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
+#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
+#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
+#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
+
+#define SI5351_CLK3_0_DISABLE_STATE		24
+#define SI5351_CLK7_4_DISABLE_STATE		25
+#define  SI5351_CLK_DISABLE_STATE_LOW		0
+#define  SI5351_CLK_DISABLE_STATE_HIGH		1
+#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
+#define  SI5351_CLK_DISABLE_STATE_NEVER		3
+
+#define SI5351_PARAMETERS_LENGTH		8
+#define SI5351_PLLA_PARAMETERS			26
+#define SI5351_PLLB_PARAMETERS			34
+#define SI5351_CLK0_PARAMETERS			42
+#define SI5351_CLK1_PARAMETERS			50
+#define SI5351_CLK2_PARAMETERS			58
+#define SI5351_CLK3_PARAMETERS			66
+#define SI5351_CLK4_PARAMETERS			74
+#define SI5351_CLK5_PARAMETERS			82
+#define SI5351_CLK6_PARAMETERS			90
+#define SI5351_CLK7_PARAMETERS			91
+#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
+#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
+#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
+#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
+#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
+#define  SI5351_OUTPUT_CLK_DIV_1		0
+#define  SI5351_OUTPUT_CLK_DIV_2		1
+#define  SI5351_OUTPUT_CLK_DIV_4		2
+#define  SI5351_OUTPUT_CLK_DIV_8		3
+#define  SI5351_OUTPUT_CLK_DIV_16		4
+#define  SI5351_OUTPUT_CLK_DIV_32		5
+#define  SI5351_OUTPUT_CLK_DIV_64		6
+#define  SI5351_OUTPUT_CLK_DIV_128		7
+#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
+
+#define SI5351_SSC_PARAM0			149
+#define SI5351_SSC_PARAM1			150
+#define SI5351_SSC_PARAM2			151
+#define SI5351_SSC_PARAM3			152
+#define SI5351_SSC_PARAM4			153
+#define SI5351_SSC_PARAM5			154
+#define SI5351_SSC_PARAM6			155
+#define SI5351_SSC_PARAM7			156
+#define SI5351_SSC_PARAM8			157
+#define SI5351_SSC_PARAM9			158
+#define SI5351_SSC_PARAM10			159
+#define SI5351_SSC_PARAM11			160
+#define SI5351_SSC_PARAM12			161
+
+#define SI5351_VXCO_PARAMETERS_LOW		162
+#define SI5351_VXCO_PARAMETERS_MID		163
+#define SI5351_VXCO_PARAMETERS_HIGH		164
+
+#define SI5351_CLK0_PHASE_OFFSET		165
+#define SI5351_CLK1_PHASE_OFFSET		166
+#define SI5351_CLK2_PHASE_OFFSET		167
+#define SI5351_CLK3_PHASE_OFFSET		168
+#define SI5351_CLK4_PHASE_OFFSET		169
+#define SI5351_CLK5_PHASE_OFFSET		170
+
+#define SI5351_PLL_RESET			177
+#define  SI5351_PLL_RESET_B			(1<<7)
+#define  SI5351_PLL_RESET_A			(1<<5)
+
+#define SI5351_CRYSTAL_LOAD			183
+#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
+#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
+#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
+#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
+
+#define SI5351_FANOUT_ENABLE			187
+#define  SI5351_CLKIN_ENABLE			(1<<7)
+#define  SI5351_XTAL_ENABLE			(1<<6)
+#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
+
+#endif
diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
new file mode 100644
index 0000000..92dabca
--- /dev/null
+++ b/include/linux/platform_data/si5351.h
@@ -0,0 +1,114 @@
+/*
+ * Si5351A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
+#define __LINUX_PLATFORM_DATA_SI5351_H__
+
+struct clk;
+
+/**
+ * enum si5351_variant - SiLabs Si5351 chip variant
+ * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
+ * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
+ * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
+ * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
+ */
+enum si5351_variant {
+	SI5351_VARIANT_A = 1,
+	SI5351_VARIANT_A3 = 2,
+	SI5351_VARIANT_B = 3,
+	SI5351_VARIANT_C = 4,
+};
+
+/**
+ * enum si5351_pll_src - Si5351 pll clock source
+ * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
+ * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
+ */
+enum si5351_pll_src {
+	SI5351_PLL_SRC_DEFAULT = 0,
+	SI5351_PLL_SRC_XTAL = 1,
+	SI5351_PLL_SRC_CLKIN = 2,
+};
+
+/**
+ * enum si5351_multisynth_src - Si5351 multisynth clock source
+ * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
+ * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
+ */
+enum si5351_multisynth_src {
+	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
+	SI5351_MULTISYNTH_SRC_VCO0 = 1,
+	SI5351_MULTISYNTH_SRC_VCO1 = 2,
+};
+
+/**
+ * enum si5351_clkout_src - Si5351 clock output clock source
+ * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
+ * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
+ * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
+ *                                or 4 (N>=4)
+ * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
+ * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
+ */
+enum si5351_clkout_src {
+	SI5351_CLKOUT_SRC_DEFAULT = 0,
+	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
+	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
+	SI5351_CLKOUT_SRC_XTAL = 3,
+	SI5351_CLKOUT_SRC_CLKIN = 4,
+};
+
+/**
+ * enum si5351_drive_strength - Si5351 clock output drive strength
+ * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
+ * @SI5351_DRIVE_2MA: 2mA clock output drive strength
+ * @SI5351_DRIVE_4MA: 4mA clock output drive strength
+ * @SI5351_DRIVE_6MA: 6mA clock output drive strength
+ * @SI5351_DRIVE_8MA: 8mA clock output drive strength
+ */
+enum si5351_drive_strength {
+	SI5351_DRIVE_DEFAULT = 0,
+	SI5351_DRIVE_2MA = 2,
+	SI5351_DRIVE_4MA = 4,
+	SI5351_DRIVE_6MA = 6,
+	SI5351_DRIVE_8MA = 8,
+};
+
+/**
+ * struct si5351_clkout_config - Si5351 clock output configuration
+ * @clkout: clkout number
+ * @multisynth_src: multisynth source clock
+ * @clkout_src: clkout source clock
+ * @pll_master: if true, clkout can also change pll rate
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5351_clkout_config {
+	enum si5351_multisynth_src multisynth_src;
+	enum si5351_clkout_src clkout_src;
+	enum si5351_drive_strength drive;
+	bool pll_master;
+	unsigned long rate;
+};
+
+/**
+ * struct si5351_platform_data - Platform data for the Si5351 clock driver
+ * @variant: Si5351 chip variant
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @pll_src: array of pll source clock setting
+ * @clkout: array of clkout configuration
+ */
+struct si5351_platform_data {
+	enum si5351_variant variant;
+	struct clk *clk_xtal;
+	struct clk *clk_clkin;
+	enum si5351_pll_src pll_src[2];
+	struct si5351_clkout_config clkout[8];
+};
+
+#endif
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v8] clk: add si5351 i2c common clock driver
@ 2013-04-12 11:43               ` Michal Bachraty
  0 siblings, 0 replies; 95+ messages in thread
From: Michal Bachraty @ 2013-04-12 11:43 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Mike Turquette,
	Stephen Warren, Thierry Reding, Dom Cobley, Linus Walleij,
	Arnd Bergmann, Andrew Morton, Pawel Moll, Mark Brown,
	Russell King - ARM Linux, Rabeeh Khoury, Daniel Mack,
	Jean-Francois Moine, Lars-Peter Clausen, Guenter Roeck,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

On Thursday, April 11, 2013 21:42:29 Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.

Tested-by: Michal Bachraty <michal.bachraty@streamunlimited.com>

Many thanks,

Michal

> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Tested-by: Daniel Mack <zonque@gmail.com>
> Acked-by: Guenter Roeck <linux@roeck-us.net>
> ---
> Changes from v7:
> - readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter
> Clausen) - check return value of of_clk_add_provider (Reported by
> Lars-Peter Clausen) - clean up i2c client init (Reported by Lars-Peter
> Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1510
> ++++++++++++++++++++ drivers/clk/clk-si5351.h                           | 
> 155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1904 insertions(+)
>  create mode 100644
> Documentation/devicetree/bindings/clock/silabs,si5351.txt create mode
> 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> b/Documentation/devicetree/bindings/clock/silabs,si5351.txt new file mode
> 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of
> {2,4,6,8}. +- silabs,multisynth-source: source pll A(0) or B(1) of
> corresponding multisynth +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator@60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		silabs,pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			silabs,drive-strength = <8>;
> +			silabs,multisynth-source = <0>;
> +			silabs,clock-source = <0>;
> +			silabs,pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			silabs,drive-strength = <4>;
> +			silabs,multisynth-source = <1>;
> +			silabs,clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			silabs,clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt
> b/Documentation/devicetree/bindings/vendor-prefixes.txt index
> 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock.
> 
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..8927284
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1510 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	const char		*pxtal_name;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	const char		*pclkin_name;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8
> 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\n", reg);
> +		return 0;
> +	}
> +
> +	return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 count, u8 *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +				    u8 reg, u8 count, const u8 *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +				  u8 reg, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +				   u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +				    u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int
> reg) +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				int num, enum si5351_pll_src parent)
> +{
> +	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (parent == SI5351_PLL_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 2)
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C &&
> +	    parent != SI5351_PLL_SRC_XTAL)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +	return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +			     (index == 0) ? SI5351_PLL_SRC_XTAL :
> +			     SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = 
%lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = 
%lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_multisynth_src parent)
> +{
> +	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +			SI5351_CLK_PLL_SELECT);
> +	return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +			       SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, 
rate
> = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, 
rate
> = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu,
> rate = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_clkout_src parent)
> +{
> +	u8 val;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case SI5351_CLKOUT_SRC_MSYNTH_N:
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (num == 0 || num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case SI5351_CLKOUT_SRC_XTAL:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case SI5351_CLKOUT_SRC_CLKIN:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_INPUT_MASK, val);
> +	return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +	struct si5351_driver_data *drvdata, int num,
> +	enum si5351_drive_strength drive)
> +{
> +	u8 mask;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case SI5351_DRIVE_2MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +		break;
> +	case SI5351_DRIVE_4MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +		break;
> +	case SI5351_DRIVE_6MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +		break;
> +	case SI5351_DRIVE_8MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +	return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +	switch (index) {
> +	case 0:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +		break;
> +	case 1:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +		break;
> +	case 2:
> +		parent = SI5351_CLKOUT_SRC_XTAL;
> +		break;
> +	case 3:
> +		parent = SI5351_CLKOUT_SRC_CLKIN;
> +		break;
> +	}
> +
> +	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			return -EINVAL;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p) {
> +			dev_err(&client->dev,
> +				"missing pll-source for pll %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			if (pdata->variant != SI5351_VARIANT_C) {
> +				dev_err(&client->dev,
> +					"invalid parent %d for pll %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_err(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* per clkout properties */
> +	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 >= 8 ||
> +		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +			dev_err(&client->dev, "invalid clkout %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for multisynth %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				if (pdata->variant != SI5351_VARIANT_C) {
> +					dev_err(&client->dev,
> +						"invalid parent %d for clkout %d\n",
> +						val, num);
> +					return -EINVAL;
> +				}
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid drive strength %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++) {
> +		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent pll %d to %d\n",
> +				n, pdata->pll_src[n]);
> +			return ret;
> +		}
> +	}
> +
> +	for (n = 0; n < 8; n++) {
> +		ret = _si5351_msynth_reparent(drvdata, n,
> +					      pdata->clkout[n].multisynth_src);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent multisynth %d to %d\n",
> +				n, pdata->clkout[n].multisynth_src);
> +			return ret;
> +		}
> +
> +		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +							pdata->clkout[n].drive);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed set drive strength of clkout%d to %d\n",
> +				n, pdata->clkout[n].drive);
> +			return ret;
> +		}
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h
> b/include/linux/platform_data/si5351.h new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0
> (N<4) + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v8] clk: add si5351 i2c common clock driver
@ 2013-04-12 11:43               ` Michal Bachraty
  0 siblings, 0 replies; 95+ messages in thread
From: Michal Bachraty @ 2013-04-12 11:43 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: linux-doc-u79uwXL29TY76Z2rM5mHXA, Lars-Peter Clausen,
	Stephen Warren, Mike Turquette, Rabeeh Khoury, Guenter Roeck,
	Pawel Moll, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	Rob Herring, Dom Cobley, Russell King - ARM Linux,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	Jean-Francois Moine, Mark Brown,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Andrew Morton

On Thursday, April 11, 2013 21:42:29 Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.

Tested-by: Michal Bachraty <michal.bachraty-6oiIBCxl0MMjD8S081q9vkEOCMrvLtNR@public.gmane.org>

Many thanks,

Michal

> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Tested-by: Daniel Mack <zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Acked-by: Guenter Roeck <linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org>
> ---
> Changes from v7:
> - readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter
> Clausen) - check return value of of_clk_add_provider (Reported by
> Lars-Peter Clausen) - clean up i2c client init (Reported by Lars-Peter
> Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely-s3s/WqlpOiPyB63q8FvJNQ@public.gmane.org>
> Cc: Rob Herring <rob.herring-bsGFqQB8/DxBDgjK7y7TUQ@public.gmane.org>
> Cc: Rob Landley <rob-VoJi6FS/r0vR7s880joybQ@public.gmane.org>
> Cc: Mike Turquette <mturquette-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> Cc: Stephen Warren <swarren-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> Cc: Thierry Reding <thierry.reding-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>
> Cc: Dom Cobley <popcornmix-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Cc: Linus Walleij <linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> Cc: Arnd Bergmann <arnd-r2nGTMty4D4@public.gmane.org>
> Cc: Andrew Morton <akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b@public.gmane.org>
> Cc: Pawel Moll <pawel.moll-5wv7dgnIgG8@public.gmane.org>
> Cc: Mark Brown <broonie-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E@public.gmane.org>
> Cc: Russell King - ARM Linux <linux-lFZ/pmaqli7XmaaqVzeoHQ@public.gmane.org>
> Cc: Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
> Cc: Daniel Mack <zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Cc: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
> Cc: Lars-Peter Clausen <lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org>
> Cc: Guenter Roeck <linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org>
> Cc: Michal Bachraty <michal.bachraty-6oiIBCxl0MMjD8S081q9vkEOCMrvLtNR@public.gmane.org>
> Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org
> Cc: linux-doc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1510
> ++++++++++++++++++++ drivers/clk/clk-si5351.h                           | 
> 155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1904 insertions(+)
>  create mode 100644
> Documentation/devicetree/bindings/clock/silabs,si5351.txt create mode
> 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> b/Documentation/devicetree/bindings/clock/silabs,si5351.txt new file mode
> 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of
> {2,4,6,8}. +- silabs,multisynth-source: source pll A(0) or B(1) of
> corresponding multisynth +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator@60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		silabs,pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			silabs,drive-strength = <8>;
> +			silabs,multisynth-source = <0>;
> +			silabs,clock-source = <0>;
> +			silabs,pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			silabs,drive-strength = <4>;
> +			silabs,multisynth-source = <1>;
> +			silabs,clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			silabs,clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt
> b/Documentation/devicetree/bindings/vendor-prefixes.txt index
> 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock.
> 
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..8927284
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1510 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + * Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	const char		*pxtal_name;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	const char		*pclkin_name;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8
> 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\n", reg);
> +		return 0;
> +	}
> +
> +	return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 count, u8 *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +				    u8 reg, u8 count, const u8 *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +				  u8 reg, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +				   u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +				    u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int
> reg) +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				int num, enum si5351_pll_src parent)
> +{
> +	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (parent == SI5351_PLL_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 2)
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C &&
> +	    parent != SI5351_PLL_SRC_XTAL)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +	return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +			     (index == 0) ? SI5351_PLL_SRC_XTAL :
> +			     SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = 
%lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = 
%lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_multisynth_src parent)
> +{
> +	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +			SI5351_CLK_PLL_SELECT);
> +	return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +			       SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, 
rate
> = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, 
rate
> = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu,
> rate = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_clkout_src parent)
> +{
> +	u8 val;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case SI5351_CLKOUT_SRC_MSYNTH_N:
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (num == 0 || num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case SI5351_CLKOUT_SRC_XTAL:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case SI5351_CLKOUT_SRC_CLKIN:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_INPUT_MASK, val);
> +	return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +	struct si5351_driver_data *drvdata, int num,
> +	enum si5351_drive_strength drive)
> +{
> +	u8 mask;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case SI5351_DRIVE_2MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +		break;
> +	case SI5351_DRIVE_4MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +		break;
> +	case SI5351_DRIVE_6MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +		break;
> +	case SI5351_DRIVE_8MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +	return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +	switch (index) {
> +	case 0:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +		break;
> +	case 1:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +		break;
> +	case 2:
> +		parent = SI5351_CLKOUT_SRC_XTAL;
> +		break;
> +	case 3:
> +		parent = SI5351_CLKOUT_SRC_CLKIN;
> +		break;
> +	}
> +
> +	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			return -EINVAL;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p) {
> +			dev_err(&client->dev,
> +				"missing pll-source for pll %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			if (pdata->variant != SI5351_VARIANT_C) {
> +				dev_err(&client->dev,
> +					"invalid parent %d for pll %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_err(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* per clkout properties */
> +	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 >= 8 ||
> +		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +			dev_err(&client->dev, "invalid clkout %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for multisynth %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				if (pdata->variant != SI5351_VARIANT_C) {
> +					dev_err(&client->dev,
> +						"invalid parent %d for clkout %d\n",
> +						val, num);
> +					return -EINVAL;
> +				}
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid drive strength %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++) {
> +		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent pll %d to %d\n",
> +				n, pdata->pll_src[n]);
> +			return ret;
> +		}
> +	}
> +
> +	for (n = 0; n < 8; n++) {
> +		ret = _si5351_msynth_reparent(drvdata, n,
> +					      pdata->clkout[n].multisynth_src);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent multisynth %d to %d\n",
> +				n, pdata->clkout[n].multisynth_src);
> +			return ret;
> +		}
> +
> +		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +							pdata->clkout[n].drive);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed set drive strength of clkout%d to %d\n",
> +				n, pdata->clkout[n].drive);
> +			return ret;
> +		}
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + * Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
> + *
> + * This program is free software; you can redistribute  it and/or modify it
> + * under  the terms of  the GNU General  Public License as published by
> the + * Free Software Foundation;  either version 2 of the  License, or (at
> your + * option) any later version.
> + */
> +
> +#ifndef _CLK_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h
> b/include/linux/platform_data/si5351.h new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0
> (N<4) + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v8] clk: add si5351 i2c common clock driver
@ 2013-04-12 11:43               ` Michal Bachraty
  0 siblings, 0 replies; 95+ messages in thread
From: Michal Bachraty @ 2013-04-12 11:43 UTC (permalink / raw)
  To: linux-arm-kernel

On Thursday, April 11, 2013 21:42:29 Sebastian Hesselbarth wrote:
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.

Tested-by: Michal Bachraty <michal.bachraty@streamunlimited.com>

Many thanks,

Michal

> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Tested-by: Daniel Mack <zonque@gmail.com>
> Acked-by: Guenter Roeck <linux@roeck-us.net>
> ---
> Changes from v7:
> - readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter
> Clausen) - check return value of of_clk_add_provider (Reported by
> Lars-Peter Clausen) - clean up i2c client init (Reported by Lars-Peter
> Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss at lists.ozlabs.org
> Cc: linux-doc at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-arm-kernel at lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1510
> ++++++++++++++++++++ drivers/clk/clk-si5351.h                           | 
> 155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1904 insertions(+)
>  create mode 100644
> Documentation/devicetree/bindings/clock/silabs,si5351.txt create mode
> 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> b/Documentation/devicetree/bindings/clock/silabs,si5351.txt new file mode
> 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of
> {2,4,6,8}. +- silabs,multisynth-source: source pll A(0) or B(1) of
> corresponding multisynth +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +	compatible = "fixed-clock";
> +	#clock-cells = <0>;
> +	clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +	/* Si5351a msop10 i2c clock generator */
> +	si5351a: clock-generator at 60 {
> +		compatible = "silabs,si5351a-msop";
> +		reg = <0x60>;
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		#clock-cells = <1>;
> +
> +		/* connect xtal input to 25MHz reference */
> +		clocks = <&ref25>;
> +
> +		/* connect xtal input as source of pll0 and pll1 */
> +		silabs,pll-source = <0 0>, <1 0>;
> +
> +		/*
> +		 * overwrite clkout0 configuration with:
> +		 * - 8mA output drive strength
> +		 * - pll0 as clock source of multisynth0
> +		 * - multisynth0 as clock source of output divider
> +		 * - multisynth0 can change pll0
> +		 * - set initial clock frequency of 74.25MHz
> +		 */
> +		clkout0 {
> +			reg = <0>;
> +			silabs,drive-strength = <8>;
> +			silabs,multisynth-source = <0>;
> +			silabs,clock-source = <0>;
> +			silabs,pll-master;
> +			clock-frequency = <74250000>;
> +		};
> +
> +		/*
> +		 * overwrite clkout1 configuration with:
> +		 * - 4mA output drive strength
> +		 * - pll1 as clock source of multisynth1
> +		 * - multisynth1 as clock source of output divider
> +		 * - multisynth1 can change pll1
> +		 */
> +		clkout1 {
> +			reg = <1>;
> +			silabs,drive-strength = <4>;
> +			silabs,multisynth-source = <1>;
> +			silabs,clock-source = <0>;
> +			pll-master;
> +		};
> +
> +		/*
> +		 * overwrite clkout2 configuration with:
> +		 * - xtal as clock source of output divider
> +		 */
> +		clkout2 {
> +			reg = <2>;
> +			silabs,clock-source = <2>;
> +		};
> +	};
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt
> b/Documentation/devicetree/bindings/vendor-prefixes.txt index
> 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung	Samsung Semiconductor
>  sbs	Smart Battery System
>  schindler	Schindler
>  sil	Silicon Image
> +silabs	Silicon Laboratories
>  simtek
>  sirf	SiRF Technology, Inc.
>  snps 	Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>  	---help---
>  	  This driver supports Maxim 77686 crystal oscillator clock.
> 
> +config COMMON_CLK_SI5351
> +	tristate "Clock driver for SiLabs 5351A/B/C"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select RATIONAL
> +	---help---
> +	  This driver supports Silicon Labs 5351A/B/C programmable clock
> +	  generators.
> +
>  config CLK_TWL6040
>  	tristate "External McPDM functional clock from twl6040"
>  	depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)		+= x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..8927284
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1510 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +	unsigned long	p1;
> +	unsigned long	p2;
> +	unsigned long	p3;
> +	int		valid;
> +};
> +
> +struct si5351_hw_data {
> +	struct clk_hw			hw;
> +	struct si5351_driver_data	*drvdata;
> +	struct si5351_parameters	params;
> +	unsigned char			num;
> +};
> +
> +struct si5351_driver_data {
> +	enum si5351_variant	variant;
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk_onecell_data onecell;
> +
> +	struct clk		*pxtal;
> +	const char		*pxtal_name;
> +	struct clk_hw		xtal;
> +	struct clk		*pclkin;
> +	const char		*pclkin_name;
> +	struct clk_hw		clkin;
> +
> +	struct si5351_hw_data	pll[2];
> +	struct si5351_hw_data	*msynth;
> +	struct si5351_hw_data	*clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +	"xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +	"plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +	"ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +	"clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8
> 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\n", reg);
> +		return 0;
> +	}
> +
> +	return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 count, u8 *buf)
> +{
> +	return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +				   u8 reg, u8 val)
> +{
> +	return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +				    u8 reg, u8 count, const u8 *buf)
> +{
> +	return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +				  u8 reg, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +	if (num > 5)
> +		return SI5351_CLK6_PARAMETERS + (num - 6);
> +	return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +				   u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = si5351_reg_read(drvdata, reg);
> +		params->p1 = buf[0];
> +		params->p2 = 0;
> +		params->p3 = 1;
> +		break;
> +	default:
> +		si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +		params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +		params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +		params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +	}
> +	params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +				    u8 reg, struct si5351_parameters *params)
> +{
> +	u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +	switch (reg) {
> +	case SI5351_CLK6_PARAMETERS:
> +	case SI5351_CLK7_PARAMETERS:
> +		buf[0] = params->p1 & 0xff;
> +		si5351_reg_write(drvdata, reg, buf[0]);
> +		break;
> +	default:
> +		buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +		buf[1] = params->p3 & 0xff;
> +		/* save rdiv and divby4 */
> +		buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +		buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +		buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +		buf[4] = params->p1 & 0xff;
> +		buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +			((params->p2 & 0xf0000) >> 16);
> +		buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +		buf[7] = params->p2 & 0xff;
> +		si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +	}
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SI5351_DEVICE_STATUS:
> +	case SI5351_INTERRUPT_STATUS:
> +	case SI5351_PLL_RESET:
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int
> reg) +{
> +	/* reserved registers */
> +	if (reg >= 4 && reg <= 8)
> +		return false;
> +	if (reg >= 10 && reg <= 14)
> +		return false;
> +	if (reg >= 173 && reg <= 176)
> +		return false;
> +	if (reg >= 178 && reg <= 182)
> +		return false;
> +	/* read-only */
> +	if (reg == SI5351_DEVICE_STATUS)
> +		return false;
> +	return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,
> +	.max_register = 187,
> +	.writeable_reg = si5351_regmap_is_writeable,
> +	.volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, xtal);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +	.prepare = si5351_xtal_prepare,
> +	.unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +	return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +			SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +					      unsigned long parent_rate)
> +{
> +	struct si5351_driver_data *drvdata =
> +		container_of(hw, struct si5351_driver_data, clkin);
> +	unsigned long rate;
> +	unsigned char idiv;
> +
> +	rate = parent_rate;
> +	if (parent_rate > 160000000) {
> +		idiv = SI5351_CLKIN_DIV_8;
> +		rate /= 8;
> +	} else if (parent_rate > 80000000) {
> +		idiv = SI5351_CLKIN_DIV_4;
> +		rate /= 4;
> +	} else if (parent_rate > 40000000) {
> +		idiv = SI5351_CLKIN_DIV_2;
> +		rate /= 2;
> +	} else {
> +		idiv = SI5351_CLKIN_DIV_1;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +			SI5351_CLKIN_DIV_MASK, idiv);
> +
> +	dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +		__func__, (1 << (idiv >> 6)), rate);
> +
> +	return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +	.prepare = si5351_clkin_prepare,
> +	.unprepare = si5351_clkin_unprepare,
> +	.recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +	return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +					     unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long parent)
> +{
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +	.prepare = si5351_vxco_prepare,
> +	.unprepare = si5351_vxco_unprepare,
> +	.recalc_rate = si5351_vxco_recalc_rate,
> +	.set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +				int num, enum si5351_pll_src parent)
> +{
> +	u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +	if (parent == SI5351_PLL_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 2)
> +		return -EINVAL;
> +
> +	if (drvdata->variant != SI5351_VARIANT_C &&
> +	    parent != SI5351_PLL_SRC_XTAL)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +			(parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +	return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +	    index > 0)
> +		return -EPERM;
> +
> +	if (index > 1)
> +		return -EINVAL;
> +
> +	return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +			     (index == 0) ? SI5351_PLL_SRC_XTAL :
> +			     SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +	unsigned long long rate;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +	rate  = hwdata->params.p1 * hwdata->params.p3;
> +	rate += 512 * hwdata->params.p3;
> +	rate += hwdata->params.p2;
> +	rate *= parent_rate;
> +	do_div(rate, 128 * hwdata->params.p3);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = 
%lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long rfrac, denom, a, b, c;
> +	unsigned long long lltmp;
> +
> +	if (rate < SI5351_PLL_VCO_MIN)
> +		rate = SI5351_PLL_VCO_MIN;
> +	if (rate > SI5351_PLL_VCO_MAX)
> +		rate = SI5351_PLL_VCO_MAX;
> +
> +	/* determine integer part of feedback equation */
> +	a = rate / *parent_rate;
> +
> +	if (a < SI5351_PLL_A_MIN)
> +		rate = *parent_rate * SI5351_PLL_A_MIN;
> +	if (a > SI5351_PLL_A_MAX)
> +		rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +	/* find best approximation for b/c = fVCO mod fIN */
> +	denom = 1000 * 1000;
> +	lltmp = rate % (*parent_rate);
> +	lltmp *= denom;
> +	do_div(lltmp, *parent_rate);
> +	rfrac = (unsigned long)lltmp;
> +
> +	b = 0;
> +	c = 1;
> +	if (rfrac)
> +		rational_best_approximation(rfrac, denom,
> +				    SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +	/* calculate parameters */
> +	hwdata->params.p3  = c;
> +	hwdata->params.p2  = (128 * b) % c;
> +	hwdata->params.p1  = 128 * a;
> +	hwdata->params.p1 += (128 * b / c);
> +	hwdata->params.p1 -= 512;
> +
> +	/* recalculate rate by fIN * (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= b;
> +	do_div(lltmp, c);
> +
> +	rate  = (unsigned long)lltmp;
> +	rate += *parent_rate * a;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +		SI5351_PLLB_PARAMETERS;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	/* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +		SI5351_CLK_INTEGER_MODE,
> +		(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = 
%lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +	.set_parent = si5351_pll_set_parent,
> +	.get_parent = si5351_pll_get_parent,
> +	.recalc_rate = si5351_pll_recalc_rate,
> +	.round_rate = si5351_pll_round_rate,
> +	.set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_multisynth_src parent)
> +{
> +	if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +		return 0;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +			(parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +			SI5351_CLK_PLL_SELECT);
> +	return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +	return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +			       (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +			       SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	unsigned long long rate;
> +	unsigned long m;
> +
> +	if (!hwdata->params.valid)
> +		si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (hwdata->params.p3 == 0)
> +		return parent_rate;
> +
> +	/*
> +	 * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +	 * multisync6-7: fOUT = fIN / P1
> +	 */
> +	rate = parent_rate;
> +	if (hwdata->num > 5) {
> +		m = hwdata->params.p1;
> +	} else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +		    SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +		m = 4;
> +	} else {
> +		rate *= 128 * hwdata->params.p3;
> +		m = hwdata->params.p1 * hwdata->params.p3;
> +		m += hwdata->params.p2;
> +		m += 512 * hwdata->params.p3;
> +	}
> +
> +	if (m == 0)
> +		return 0;
> +	do_div(rate, m);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, 
rate
> = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		m, parent_rate, (unsigned long)rate);
> +
> +	return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long long lltmp;
> +	unsigned long a, b, c;
> +	int divby4;
> +
> +	/* multisync6-7 can only handle freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +	/* multisync frequency is 1MHz .. 160MHz */
> +	if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +		rate = SI5351_MULTISYNTH_MAX_FREQ;
> +	if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +		rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +	divby4 = 0;
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* multisync can set pll */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/*
> +		 * find largest integer divider for max
> +		 * vco frequency and given target rate
> +		 */
> +		if (divby4 == 0) {
> +			lltmp = SI5351_PLL_VCO_MAX;
> +			do_div(lltmp, rate);
> +			a = (unsigned long)lltmp;
> +		} else
> +			a = 4;
> +
> +		b = 0;
> +		c = 1;
> +
> +		*parent_rate = a * rate;
> +	} else {
> +		unsigned long rfrac, denom;
> +
> +		/* disable divby4 */
> +		if (divby4) {
> +			rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +			divby4 = 0;
> +		}
> +
> +		/* determine integer part of divider equation */
> +		a = *parent_rate / rate;
> +		if (a < SI5351_MULTISYNTH_A_MIN)
> +			a = SI5351_MULTISYNTH_A_MIN;
> +		if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +			a = SI5351_MULTISYNTH67_A_MAX;
> +		else if (a > SI5351_MULTISYNTH_A_MAX)
> +			a = SI5351_MULTISYNTH_A_MAX;
> +
> +		/* find best approximation for b/c = fVCO mod fOUT */
> +		denom = 1000 * 1000;
> +		lltmp = (*parent_rate) % rate;
> +		lltmp *= denom;
> +		do_div(lltmp, rate);
> +		rfrac = (unsigned long)lltmp;
> +
> +		b = 0;
> +		c = 1;
> +		if (rfrac)
> +			rational_best_approximation(rfrac, denom,
> +			    SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +			    &b, &c);
> +	}
> +
> +	/* recalculate rate by fOUT = fIN / (a + b/c) */
> +	lltmp  = *parent_rate;
> +	lltmp *= c;
> +	do_div(lltmp, a * c + b);
> +	rate  = (unsigned long)lltmp;
> +
> +	/* calculate parameters */
> +	if (divby4) {
> +		hwdata->params.p3 = 1;
> +		hwdata->params.p2 = 0;
> +		hwdata->params.p1 = 0;
> +	} else {
> +		hwdata->params.p3  = c;
> +		hwdata->params.p2  = (128 * b) % c;
> +		hwdata->params.p1  = 128 * a;
> +		hwdata->params.p1 += (128 * b / c);
> +		hwdata->params.p1 -= 512;
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, 
rate
> = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	u8 reg = si5351_msynth_params_address(hwdata->num);
> +	int divby4 = 0;
> +
> +	/* write multisynth parameters */
> +	si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +	if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +		divby4 = 1;
> +
> +	/* enable/disable integer mode and divby4 on multisynth0-5 */
> +	if (hwdata->num < 6) {
> +		si5351_set_bits(hwdata->drvdata, reg + 2,
> +				SI5351_OUTPUT_CLK_DIVBY4,
> +				(divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_INTEGER_MODE,
> +			(hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +	}
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu,
> rate = %lu\n", +		__func__, __clk_get_name(hwdata->hw.clk),
> +		hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +		divby4, parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +	.set_parent = si5351_msynth_set_parent,
> +	.get_parent = si5351_msynth_get_parent,
> +	.recalc_rate = si5351_msynth_recalc_rate,
> +	.round_rate = si5351_msynth_round_rate,
> +	.set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +				   int num, enum si5351_clkout_src parent)
> +{
> +	u8 val;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (parent) {
> +	case SI5351_CLKOUT_SRC_MSYNTH_N:
> +		val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		break;
> +	case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +		/* clk0/clk4 can only connect to its own multisync */
> +		if (num == 0 || num == 4)
> +			val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +		else
> +			val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +		break;
> +	case SI5351_CLKOUT_SRC_XTAL:
> +		val = SI5351_CLK_INPUT_XTAL;
> +		break;
> +	case SI5351_CLKOUT_SRC_CLKIN:
> +		if (drvdata->variant != SI5351_VARIANT_C)
> +			return -EINVAL;
> +
> +		val = SI5351_CLK_INPUT_CLKIN;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_INPUT_MASK, val);
> +	return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +	struct si5351_driver_data *drvdata, int num,
> +	enum si5351_drive_strength drive)
> +{
> +	u8 mask;
> +
> +	if (num > 8)
> +		return -EINVAL;
> +
> +	switch (drive) {
> +	case SI5351_DRIVE_2MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +		break;
> +	case SI5351_DRIVE_4MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +		break;
> +	case SI5351_DRIVE_6MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +		break;
> +	case SI5351_DRIVE_8MA:
> +		mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +		break;
> +	default:
> +		return 0;
> +	}
> +
> +	si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +			SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +	return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), 0);
> +	return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +	si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +			(1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	int index = 0;
> +	unsigned char val;
> +
> +	val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +	switch (val & SI5351_CLK_INPUT_MASK) {
> +	case SI5351_CLK_INPUT_MULTISYNTH_N:
> +		index = 0;
> +		break;
> +	case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +		index = 1;
> +		break;
> +	case SI5351_CLK_INPUT_XTAL:
> +		index = 2;
> +		break;
> +	case SI5351_CLK_INPUT_CLKIN:
> +		index = 3;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +	switch (index) {
> +	case 0:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +		break;
> +	case 1:
> +		parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +		break;
> +	case 2:
> +		parent = SI5351_CLKOUT_SRC_XTAL;
> +		break;
> +	case 3:
> +		parent = SI5351_CLKOUT_SRC_CLKIN;
> +		break;
> +	}
> +
> +	return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char reg;
> +	unsigned char rdiv;
> +
> +	if (hwdata->num > 5)
> +		reg = si5351_msynth_params_address(hwdata->num) + 2;
> +	else
> +		reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +	rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +	if (hwdata->num == 6) {
> +		rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +	} else {
> +		rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +		rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +	}
> +
> +	return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +				     unsigned long *parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned char rdiv;
> +
> +	/* clkout6/7 can only handle output freqencies < 150MHz */
> +	if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +		rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +	/* clkout freqency is 8kHz - 160MHz */
> +	if (rate > SI5351_CLKOUT_MAX_FREQ)
> +		rate = SI5351_CLKOUT_MAX_FREQ;
> +	if (rate < SI5351_CLKOUT_MIN_FREQ)
> +		rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +	/* request frequency if multisync master */
> +	if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +		/* use r divider for frequencies below 1MHz */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +		       rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +			rdiv += 1;
> +			rate *= 2;
> +		}
> +		*parent_rate = rate;
> +	} else {
> +		unsigned long new_rate, new_err, err;
> +
> +		/* round to closed rdiv */
> +		rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +		new_rate = *parent_rate;
> +		err = abs(new_rate - rate);
> +		do {
> +			new_rate >>= 1;
> +			new_err = abs(new_rate - rate);
> +			if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +				break;
> +			rdiv++;
> +			err = new_err;
> +		} while (1);
> +	}
> +	rate = *parent_rate >> rdiv;
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		*parent_rate, rate);
> +
> +	return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long parent_rate)
> +{
> +	struct si5351_hw_data *hwdata =
> +		container_of(hw, struct si5351_hw_data, hw);
> +	unsigned long new_rate, new_err, err;
> +	unsigned char rdiv;
> +
> +	/* round to closed rdiv */
> +	rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +	new_rate = parent_rate;
> +	err = abs(new_rate - rate);
> +	do {
> +		new_rate >>= 1;
> +		new_err = abs(new_rate - rate);
> +		if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +			break;
> +		rdiv++;
> +		err = new_err;
> +	} while (1);
> +
> +	/* write output divider */
> +	switch (hwdata->num) {
> +	case 6:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +		break;
> +	case 7:
> +		si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +		break;
> +	default:
> +		si5351_set_bits(hwdata->drvdata,
> +				si5351_msynth_params_address(hwdata->num) + 2,
> +				SI5351_OUTPUT_CLK_DIV_MASK,
> +				rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +	}
> +
> +	/* powerup clkout */
> +	si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +			SI5351_CLK_POWERDOWN, 0);
> +
> +	dev_dbg(&hwdata->drvdata->client->dev,
> +		"%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +		__func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +		parent_rate, rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +	.prepare = si5351_clkout_prepare,
> +	.unprepare = si5351_clkout_unprepare,
> +	.set_parent = si5351_clkout_set_parent,
> +	.get_parent = si5351_clkout_get_parent,
> +	.recalc_rate = si5351_clkout_recalc_rate,
> +	.round_rate = si5351_clkout_round_rate,
> +	.set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +	{ .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +	{ .compatible = "silabs,si5351a-msop",
> +					 .data = (void *)SI5351_VARIANT_A3, },
> +	{ .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +	{ .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	struct device_node *child, *np = client->dev.of_node;
> +	struct si5351_platform_data *pdata;
> +	const struct of_device_id *match;
> +	struct property *prop;
> +	const __be32 *p;
> +	int num = 0;
> +	u32 val;
> +
> +	if (np == NULL)
> +		return 0;
> +
> +	match = of_match_node(si5351_dt_ids, np);
> +	if (match == NULL)
> +		return -EINVAL;
> +
> +	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->variant = (enum si5351_variant)match->data;
> +	pdata->clk_xtal = of_clk_get(np, 0);
> +	if (!IS_ERR(pdata->clk_xtal))
> +		clk_put(pdata->clk_xtal);
> +	pdata->clk_clkin = of_clk_get(np, 1);
> +	if (!IS_ERR(pdata->clk_clkin))
> +		clk_put(pdata->clk_clkin);
> +
> +	/*
> +	 * property silabs,pll-source : <num src>, [<..>]
> +	 * allow to selectively set pll source
> +	 */
> +	of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +		if (num >= 2) {
> +			dev_err(&client->dev,
> +				"invalid pll %d on pll-source prop\n", num);
> +			return -EINVAL;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &val);
> +		if (!p) {
> +			dev_err(&client->dev,
> +				"missing pll-source for pll %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		switch (val) {
> +		case 0:
> +			pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +			break;
> +		case 1:
> +			if (pdata->variant != SI5351_VARIANT_C) {
> +				dev_err(&client->dev,
> +					"invalid parent %d for pll %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +			pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +			break;
> +		default:
> +			dev_err(&client->dev,
> +				 "invalid parent %d for pll %d\n", val, num);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* per clkout properties */
> +	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 >= 8 ||
> +		    (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +			dev_err(&client->dev, "invalid clkout %d\n", num);
> +			return -EINVAL;
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,multisynth-source",
> +					  &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO0;
> +				break;
> +			case 1:
> +				pdata->clkout[num].multisynth_src =
> +					SI5351_MULTISYNTH_SRC_VCO1;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for multisynth %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +			switch (val) {
> +			case 0:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_N;
> +				break;
> +			case 1:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +				break;
> +			case 2:
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_XTAL;
> +				break;
> +			case 3:
> +				if (pdata->variant != SI5351_VARIANT_C) {
> +					dev_err(&client->dev,
> +						"invalid parent %d for clkout %d\n",
> +						val, num);
> +					return -EINVAL;
> +				}
> +				pdata->clkout[num].clkout_src =
> +					SI5351_CLKOUT_SRC_CLKIN;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid parent %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "silabs,drive-strength",
> +					  &val)) {
> +			switch (val) {
> +			case SI5351_DRIVE_2MA:
> +			case SI5351_DRIVE_4MA:
> +			case SI5351_DRIVE_6MA:
> +			case SI5351_DRIVE_8MA:
> +				pdata->clkout[num].drive = val;
> +				break;
> +			default:
> +				dev_err(&client->dev,
> +					"invalid drive strength %d for clkout %d\n",
> +					val, num);
> +				return -EINVAL;
> +			}
> +		}
> +
> +		if (!of_property_read_u32(child, "clock-frequency", &val))
> +			pdata->clkout[num].rate = val;
> +
> +		pdata->clkout[num].pll_master =
> +			of_property_read_bool(child, "silabs,pll-master");
> +	}
> +	client->dev.platform_data = pdata;
> +
> +	return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +			    const struct i2c_device_id *id)
> +{
> +	struct si5351_platform_data *pdata;
> +	struct si5351_driver_data *drvdata;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	const char *parent_names[4];
> +	u8 num_parents, num_clocks;
> +	int ret, n;
> +
> +	ret = si5351_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) {
> +		dev_err(&client->dev, "unable to allocate driver data\n");
> +		return -ENOMEM;
> +	}
> +
> +	i2c_set_clientdata(client, drvdata);
> +	drvdata->client = client;
> +	drvdata->variant = pdata->variant;
> +	drvdata->pxtal = pdata->clk_xtal;
> +	drvdata->pclkin = pdata->clk_clkin;
> +
> +	drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +	if (IS_ERR(drvdata->regmap)) {
> +		dev_err(&client->dev, "failed to allocate register map\n");
> +		return PTR_ERR(drvdata->regmap);
> +	}
> +
> +	/* Disable interrupts */
> +	si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +	/* Set disabled output drivers to drive low */
> +	si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +	si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +	/* Ensure pll select is on XTAL for Si5351A/B */
> +	if (drvdata->variant != SI5351_VARIANT_C)
> +		si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +				SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +	/* setup clock configuration */
> +	for (n = 0; n < 2; n++) {
> +		ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent pll %d to %d\n",
> +				n, pdata->pll_src[n]);
> +			return ret;
> +		}
> +	}
> +
> +	for (n = 0; n < 8; n++) {
> +		ret = _si5351_msynth_reparent(drvdata, n,
> +					      pdata->clkout[n].multisynth_src);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed to reparent multisynth %d to %d\n",
> +				n, pdata->clkout[n].multisynth_src);
> +			return ret;
> +		}
> +
> +		ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +							pdata->clkout[n].drive);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"failed set drive strength of clkout%d to %d\n",
> +				n, pdata->clkout[n].drive);
> +			return ret;
> +		}
> +	}
> +
> +	/* register xtal input clock gate */
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_input_names[0];
> +	init.ops = &si5351_xtal_ops;
> +	init.flags = 0;
> +	if (!IS_ERR(drvdata->pxtal)) {
> +		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +		init.parent_names = &drvdata->pxtal_name;
> +		init.num_parents = 1;
> +	}
> +	drvdata->xtal.init = &init;
> +	clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return PTR_ERR(clk);
> +	}
> +
> +	/* register clkin input clock gate */
> +	if (drvdata->variant == SI5351_VARIANT_C) {
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_input_names[1];
> +		init.ops = &si5351_clkin_ops;
> +		if (!IS_ERR(drvdata->pclkin)) {
> +			drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +			init.parent_names = &drvdata->pclkin_name;
> +			init.num_parents = 1;
> +		}
> +		drvdata->clkin.init = &init;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return PTR_ERR(clk);
> +		}
> +	}
> +
> +	/* Si5351C allows to mux either xtal or clkin to PLL input */
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +	parent_names[0] = si5351_input_names[0];
> +	parent_names[1] = si5351_input_names[1];
> +
> +	/* register PLLA */
> +	drvdata->pll[0].num = 0;
> +	drvdata->pll[0].drvdata = drvdata;
> +	drvdata->pll[0].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	init.name = si5351_pll_names[0];
> +	init.ops = &si5351_pll_ops;
> +	init.flags = 0;
> +	init.parent_names = parent_names;
> +	init.num_parents = num_parents;
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register PLLB or VXCO (Si5351B) */
> +	drvdata->pll[1].num = 1;
> +	drvdata->pll[1].drvdata = drvdata;
> +	drvdata->pll[1].hw.init = &init;
> +	memset(&init, 0, sizeof(init));
> +	if (drvdata->variant == SI5351_VARIANT_B) {
> +		init.name = si5351_pll_names[2];
> +		init.ops = &si5351_vxco_ops;
> +		init.flags = CLK_IS_ROOT;
> +		init.parent_names = NULL;
> +		init.num_parents = 0;
> +	} else {
> +		init.name = si5351_pll_names[1];
> +		init.ops = &si5351_pll_ops;
> +		init.flags = 0;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +	}
> +	clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +	if (IS_ERR(clk)) {
> +		dev_err(&client->dev, "unable to register %s\n", init.name);
> +		return -EINVAL;
> +	}
> +
> +	/* register clk multisync and clk out divider */
> +	num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +	parent_names[0] = si5351_pll_names[0];
> +	if (drvdata->variant == SI5351_VARIANT_B)
> +		parent_names[1] = si5351_pll_names[2];
> +	else
> +		parent_names[1] = si5351_pll_names[1];
> +
> +	drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->msynth), GFP_KERNEL);
> +	drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +				       sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +	drvdata->onecell.clk_num = num_clocks;
> +	drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +		num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +		    !drvdata->onecell.clks))
> +		return -ENOMEM;
> +
> +	for (n = 0; n < num_clocks; n++) {
> +		drvdata->msynth[n].num = n;
> +		drvdata->msynth[n].drvdata = drvdata;
> +		drvdata->msynth[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_msynth_names[n];
> +		init.ops = &si5351_msynth_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].pll_master)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = 2;
> +		clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +	parent_names[2] = si5351_input_names[0];
> +	parent_names[3] = si5351_input_names[1];
> +	for (n = 0; n < num_clocks; n++) {
> +		parent_names[0] = si5351_msynth_names[n];
> +		parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +			si5351_msynth_names[4];
> +
> +		drvdata->clkout[n].num = n;
> +		drvdata->clkout[n].drvdata = drvdata;
> +		drvdata->clkout[n].hw.init = &init;
> +		memset(&init, 0, sizeof(init));
> +		init.name = si5351_clkout_names[n];
> +		init.ops = &si5351_clkout_ops;
> +		init.flags = 0;
> +		if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +			init.flags |= CLK_SET_RATE_PARENT;
> +		init.parent_names = parent_names;
> +		init.num_parents = num_parents;
> +		clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +		if (IS_ERR(clk)) {
> +			dev_err(&client->dev, "unable to register %s\n",
> +				init.name);
> +			return -EINVAL;
> +		}
> +		drvdata->onecell.clks[n] = clk;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +	{ "silabs,si5351", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +	.driver = {
> +		.name = "si5351",
> +		.of_match_table = of_match_ptr(si5351_dt_ids),
> +	},
> +	.probe = si5351_i2c_probe,
> +	.id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR			0x60
> +
> +#define SI5351_PLL_VCO_MIN			600000000
> +#define SI5351_PLL_VCO_MAX			900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ		1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ		150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ		160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ		SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ			8000
> +#define SI5351_CLKOUT_MAX_FREQ			SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ		SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN			15
> +#define SI5351_PLL_A_MAX			90
> +#define SI5351_PLL_B_MAX			(SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX			1048575
> +#define SI5351_MULTISYNTH_A_MIN			6
> +#define SI5351_MULTISYNTH_A_MAX			1800
> +#define SI5351_MULTISYNTH67_A_MAX		254
> +#define SI5351_MULTISYNTH_B_MAX			(SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX			1048575
> +#define SI5351_MULTISYNTH_P1_MAX		((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX		((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX		((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS			0
> +#define SI5351_INTERRUPT_STATUS			1
> +#define SI5351_INTERRUPT_MASK			2
> +#define  SI5351_STATUS_SYS_INIT			(1<<7)
> +#define  SI5351_STATUS_LOL_B			(1<<6)
> +#define  SI5351_STATUS_LOL_A			(1<<5)
> +#define  SI5351_STATUS_LOS			(1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL		3
> +#define SI5351_OEB_PIN_ENABLE_CTRL		9
> +#define SI5351_PLL_INPUT_SOURCE			15
> +#define  SI5351_CLKIN_DIV_MASK			(3<<6)
> +#define  SI5351_CLKIN_DIV_1			(0<<6)
> +#define  SI5351_CLKIN_DIV_2			(1<<6)
> +#define  SI5351_CLKIN_DIV_4			(2<<6)
> +#define  SI5351_CLKIN_DIV_8			(3<<6)
> +#define  SI5351_PLLB_SOURCE			(1<<3)
> +#define  SI5351_PLLA_SOURCE			(1<<2)
> +
> +#define SI5351_CLK0_CTRL			16
> +#define SI5351_CLK1_CTRL			17
> +#define SI5351_CLK2_CTRL			18
> +#define SI5351_CLK3_CTRL			19
> +#define SI5351_CLK4_CTRL			20
> +#define SI5351_CLK5_CTRL			21
> +#define SI5351_CLK6_CTRL			22
> +#define SI5351_CLK7_CTRL			23
> +#define  SI5351_CLK_POWERDOWN			(1<<7)
> +#define  SI5351_CLK_INTEGER_MODE		(1<<6)
> +#define  SI5351_CLK_PLL_SELECT			(1<<5)
> +#define  SI5351_CLK_INVERT			(1<<4)
> +#define  SI5351_CLK_INPUT_MASK			(3<<2)
> +#define  SI5351_CLK_INPUT_XTAL			(0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN			(1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4	(2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N		(3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK		(3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA		(0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA		(1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA		(2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA		(3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE		24
> +#define SI5351_CLK7_4_DISABLE_STATE		25
> +#define  SI5351_CLK_DISABLE_STATE_LOW		0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH		1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT		2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER		3
> +
> +#define SI5351_PARAMETERS_LENGTH		8
> +#define SI5351_PLLA_PARAMETERS			26
> +#define SI5351_PLLB_PARAMETERS			34
> +#define SI5351_CLK0_PARAMETERS			42
> +#define SI5351_CLK1_PARAMETERS			50
> +#define SI5351_CLK2_PARAMETERS			58
> +#define SI5351_CLK3_PARAMETERS			66
> +#define SI5351_CLK4_PARAMETERS			74
> +#define SI5351_CLK5_PARAMETERS			82
> +#define SI5351_CLK6_PARAMETERS			90
> +#define SI5351_CLK7_PARAMETERS			91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER		92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK		(7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK		(7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT		4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT		0
> +#define  SI5351_OUTPUT_CLK_DIV_1		0
> +#define  SI5351_OUTPUT_CLK_DIV_2		1
> +#define  SI5351_OUTPUT_CLK_DIV_4		2
> +#define  SI5351_OUTPUT_CLK_DIV_8		3
> +#define  SI5351_OUTPUT_CLK_DIV_16		4
> +#define  SI5351_OUTPUT_CLK_DIV_32		5
> +#define  SI5351_OUTPUT_CLK_DIV_64		6
> +#define  SI5351_OUTPUT_CLK_DIV_128		7
> +#define  SI5351_OUTPUT_CLK_DIVBY4		(3<<2)
> +
> +#define SI5351_SSC_PARAM0			149
> +#define SI5351_SSC_PARAM1			150
> +#define SI5351_SSC_PARAM2			151
> +#define SI5351_SSC_PARAM3			152
> +#define SI5351_SSC_PARAM4			153
> +#define SI5351_SSC_PARAM5			154
> +#define SI5351_SSC_PARAM6			155
> +#define SI5351_SSC_PARAM7			156
> +#define SI5351_SSC_PARAM8			157
> +#define SI5351_SSC_PARAM9			158
> +#define SI5351_SSC_PARAM10			159
> +#define SI5351_SSC_PARAM11			160
> +#define SI5351_SSC_PARAM12			161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW		162
> +#define SI5351_VXCO_PARAMETERS_MID		163
> +#define SI5351_VXCO_PARAMETERS_HIGH		164
> +
> +#define SI5351_CLK0_PHASE_OFFSET		165
> +#define SI5351_CLK1_PHASE_OFFSET		166
> +#define SI5351_CLK2_PHASE_OFFSET		167
> +#define SI5351_CLK3_PHASE_OFFSET		168
> +#define SI5351_CLK4_PHASE_OFFSET		169
> +#define SI5351_CLK5_PHASE_OFFSET		170
> +
> +#define SI5351_PLL_RESET			177
> +#define  SI5351_PLL_RESET_B			(1<<7)
> +#define  SI5351_PLL_RESET_A			(1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD			183
> +#define  SI5351_CRYSTAL_LOAD_MASK		(3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF		(1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF		(2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF		(3<<6)
> +
> +#define SI5351_FANOUT_ENABLE			187
> +#define  SI5351_CLKIN_ENABLE			(1<<7)
> +#define  SI5351_XTAL_ENABLE			(1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE		(1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h
> b/include/linux/platform_data/si5351.h new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +	SI5351_VARIANT_A = 1,
> +	SI5351_VARIANT_A3 = 2,
> +	SI5351_VARIANT_B = 3,
> +	SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +	SI5351_PLL_SRC_DEFAULT = 0,
> +	SI5351_PLL_SRC_XTAL = 1,
> +	SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +	SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +	SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +	SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0
> (N<4) + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +	SI5351_CLKOUT_SRC_DEFAULT = 0,
> +	SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +	SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +	SI5351_CLKOUT_SRC_XTAL = 3,
> +	SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +	SI5351_DRIVE_DEFAULT = 0,
> +	SI5351_DRIVE_2MA = 2,
> +	SI5351_DRIVE_4MA = 4,
> +	SI5351_DRIVE_6MA = 6,
> +	SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +	enum si5351_multisynth_src multisynth_src;
> +	enum si5351_clkout_src clkout_src;
> +	enum si5351_drive_strength drive;
> +	bool pll_master;
> +	unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +	enum si5351_variant variant;
> +	struct clk *clk_xtal;
> +	struct clk *clk_clkin;
> +	enum si5351_pll_src pll_src[2];
> +	struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v8] clk: add si5351 i2c common clock driver
  2013-04-11 19:42             ` Sebastian Hesselbarth
  (?)
@ 2013-04-12 18:19               ` Mike Turquette
  -1 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-12 18:19 UTC (permalink / raw)
  To: Sebastian Hesselbarth, Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Pawel Moll, Mark Brown, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	Lars-Peter Clausen, Guenter Roeck, Michal Bachraty,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-04-11 12:42:29)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Tested-by: Daniel Mack <zonque@gmail.com>
> Acked-by: Guenter Roeck <linux@roeck-us.net>

Made it under the wire!  Thanks for cooking up v8 so quickly.  I've
taken this into clk-next.  It's cool to have a clk driver for a discrete
clock generator device.

Regards,
Mike

> ---
> Changes from v7:
> - readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
> - check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
> - clean up i2c client init (Reported by Lars-Peter Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1510 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1904 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +       compatible = "fixed-clock";
> +       #clock-cells = <0>;
> +       clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +       /* Si5351a msop10 i2c clock generator */
> +       si5351a: clock-generator@60 {
> +               compatible = "silabs,si5351a-msop";
> +               reg = <0x60>;
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +               #clock-cells = <1>;
> +
> +               /* connect xtal input to 25MHz reference */
> +               clocks = <&ref25>;
> +
> +               /* connect xtal input as source of pll0 and pll1 */
> +               silabs,pll-source = <0 0>, <1 0>;
> +
> +               /*
> +                * overwrite clkout0 configuration with:
> +                * - 8mA output drive strength
> +                * - pll0 as clock source of multisynth0
> +                * - multisynth0 as clock source of output divider
> +                * - multisynth0 can change pll0
> +                * - set initial clock frequency of 74.25MHz
> +                */
> +               clkout0 {
> +                       reg = <0>;
> +                       silabs,drive-strength = <8>;
> +                       silabs,multisynth-source = <0>;
> +                       silabs,clock-source = <0>;
> +                       silabs,pll-master;
> +                       clock-frequency = <74250000>;
> +               };
> +
> +               /*
> +                * overwrite clkout1 configuration with:
> +                * - 4mA output drive strength
> +                * - pll1 as clock source of multisynth1
> +                * - multisynth1 as clock source of output divider
> +                * - multisynth1 can change pll1
> +                */
> +               clkout1 {
> +                       reg = <1>;
> +                       silabs,drive-strength = <4>;
> +                       silabs,multisynth-source = <1>;
> +                       silabs,clock-source = <0>;
> +                       pll-master;
> +               };
> +
> +               /*
> +                * overwrite clkout2 configuration with:
> +                * - xtal as clock source of output divider
> +                */
> +               clkout2 {
> +                       reg = <2>;
> +                       silabs,clock-source = <2>;
> +               };
> +       };
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung       Samsung Semiconductor
>  sbs    Smart Battery System
>  schindler      Schindler
>  sil    Silicon Image
> +silabs Silicon Laboratories
>  simtek
>  sirf   SiRF Technology, Inc.
>  snps   Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>         ---help---
>           This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +       tristate "Clock driver for SiLabs 5351A/B/C"
> +       depends on I2C
> +       select REGMAP_I2C
> +       select RATIONAL
> +       ---help---
> +         This driver supports Silicon Labs 5351A/B/C programmable clock
> +         generators.
> +
>  config CLK_TWL6040
>         tristate "External McPDM functional clock from twl6040"
>         depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)             += x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)      += clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..8927284
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1510 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +       unsigned long   p1;
> +       unsigned long   p2;
> +       unsigned long   p3;
> +       int             valid;
> +};
> +
> +struct si5351_hw_data {
> +       struct clk_hw                   hw;
> +       struct si5351_driver_data       *drvdata;
> +       struct si5351_parameters        params;
> +       unsigned char                   num;
> +};
> +
> +struct si5351_driver_data {
> +       enum si5351_variant     variant;
> +       struct i2c_client       *client;
> +       struct regmap           *regmap;
> +       struct clk_onecell_data onecell;
> +
> +       struct clk              *pxtal;
> +       const char              *pxtal_name;
> +       struct clk_hw           xtal;
> +       struct clk              *pclkin;
> +       const char              *pclkin_name;
> +       struct clk_hw           clkin;
> +
> +       struct si5351_hw_data   pll[2];
> +       struct si5351_hw_data   *msynth;
> +       struct si5351_hw_data   *clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +       "xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +       "plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +       "ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +       "clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
> +               return 0;
> +       }
> +
> +       return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +                                  u8 reg, u8 count, u8 *buf)
> +{
> +       return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +                                  u8 reg, u8 val)
> +{
> +       return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +                                   u8 reg, u8 count, const u8 *buf)
> +{
> +       return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +                                 u8 reg, u8 mask, u8 val)
> +{
> +       return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +       if (num > 5)
> +               return SI5351_CLK6_PARAMETERS + (num - 6);
> +       return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +                                  u8 reg, struct si5351_parameters *params)
> +{
> +       u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = si5351_reg_read(drvdata, reg);
> +               params->p1 = buf[0];
> +               params->p2 = 0;
> +               params->p3 = 1;
> +               break;
> +       default:
> +               si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +               params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +               params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +               params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +       }
> +       params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +                                   u8 reg, struct si5351_parameters *params)
> +{
> +       u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = params->p1 & 0xff;
> +               si5351_reg_write(drvdata, reg, buf[0]);
> +               break;
> +       default:
> +               buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +               buf[1] = params->p3 & 0xff;
> +               /* save rdiv and divby4 */
> +               buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +               buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +               buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +               buf[4] = params->p1 & 0xff;
> +               buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +                       ((params->p2 & 0xf0000) >> 16);
> +               buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +               buf[7] = params->p2 & 0xff;
> +               si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +       }
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +       switch (reg) {
> +       case SI5351_DEVICE_STATUS:
> +       case SI5351_INTERRUPT_STATUS:
> +       case SI5351_PLL_RESET:
> +               return true;
> +       }
> +       return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +       /* reserved registers */
> +       if (reg >= 4 && reg <= 8)
> +               return false;
> +       if (reg >= 10 && reg <= 14)
> +               return false;
> +       if (reg >= 173 && reg <= 176)
> +               return false;
> +       if (reg >= 178 && reg <= 182)
> +               return false;
> +       /* read-only */
> +       if (reg == SI5351_DEVICE_STATUS)
> +               return false;
> +       return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +       .reg_bits = 8,
> +       .val_bits = 8,
> +       .cache_type = REGCACHE_RBTREE,
> +       .max_register = 187,
> +       .writeable_reg = si5351_regmap_is_writeable,
> +       .volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +       .prepare = si5351_xtal_prepare,
> +       .unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +                                             unsigned long parent_rate)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       unsigned long rate;
> +       unsigned char idiv;
> +
> +       rate = parent_rate;
> +       if (parent_rate > 160000000) {
> +               idiv = SI5351_CLKIN_DIV_8;
> +               rate /= 8;
> +       } else if (parent_rate > 80000000) {
> +               idiv = SI5351_CLKIN_DIV_4;
> +               rate /= 4;
> +       } else if (parent_rate > 40000000) {
> +               idiv = SI5351_CLKIN_DIV_2;
> +               rate /= 2;
> +       } else {
> +               idiv = SI5351_CLKIN_DIV_1;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       SI5351_CLKIN_DIV_MASK, idiv);
> +
> +       dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +               __func__, (1 << (idiv >> 6)), rate);
> +
> +       return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +       .prepare = si5351_clkin_prepare,
> +       .unprepare = si5351_clkin_unprepare,
> +       .recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +       return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +                                            unsigned long parent_rate)
> +{
> +       return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long parent)
> +{
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +       .prepare = si5351_vxco_prepare,
> +       .unprepare = si5351_vxco_unprepare,
> +       .recalc_rate = si5351_vxco_recalc_rate,
> +       .set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +                               int num, enum si5351_pll_src parent)
> +{
> +       u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +       if (parent == SI5351_PLL_SRC_DEFAULT)
> +               return 0;
> +
> +       if (num > 2)
> +               return -EINVAL;
> +
> +       if (drvdata->variant != SI5351_VARIANT_C &&
> +           parent != SI5351_PLL_SRC_XTAL)
> +               return -EINVAL;
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +                       (parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +       return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +       u8 val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +       return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +           index > 0)
> +               return -EPERM;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +                            (index == 0) ? SI5351_PLL_SRC_XTAL :
> +                            SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +               SI5351_PLLB_PARAMETERS;
> +       unsigned long long rate;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +       rate  = hwdata->params.p1 * hwdata->params.p3;
> +       rate += 512 * hwdata->params.p3;
> +       rate += hwdata->params.p2;
> +       rate *= parent_rate;
> +       do_div(rate, 128 * hwdata->params.p3);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long rfrac, denom, a, b, c;
> +       unsigned long long lltmp;
> +
> +       if (rate < SI5351_PLL_VCO_MIN)
> +               rate = SI5351_PLL_VCO_MIN;
> +       if (rate > SI5351_PLL_VCO_MAX)
> +               rate = SI5351_PLL_VCO_MAX;
> +
> +       /* determine integer part of feedback equation */
> +       a = rate / *parent_rate;
> +
> +       if (a < SI5351_PLL_A_MIN)
> +               rate = *parent_rate * SI5351_PLL_A_MIN;
> +       if (a > SI5351_PLL_A_MAX)
> +               rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +       /* find best approximation for b/c = fVCO mod fIN */
> +       denom = 1000 * 1000;
> +       lltmp = rate % (*parent_rate);
> +       lltmp *= denom;
> +       do_div(lltmp, *parent_rate);
> +       rfrac = (unsigned long)lltmp;
> +
> +       b = 0;
> +       c = 1;
> +       if (rfrac)
> +               rational_best_approximation(rfrac, denom,
> +                                   SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +       /* calculate parameters */
> +       hwdata->params.p3  = c;
> +       hwdata->params.p2  = (128 * b) % c;
> +       hwdata->params.p1  = 128 * a;
> +       hwdata->params.p1 += (128 * b / c);
> +       hwdata->params.p1 -= 512;
> +
> +       /* recalculate rate by fIN * (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= b;
> +       do_div(lltmp, c);
> +
> +       rate  = (unsigned long)lltmp;
> +       rate += *parent_rate * a;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +               SI5351_PLLB_PARAMETERS;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       /* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +               SI5351_CLK_INTEGER_MODE,
> +               (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +       .set_parent = si5351_pll_set_parent,
> +       .get_parent = si5351_pll_get_parent,
> +       .recalc_rate = si5351_pll_recalc_rate,
> +       .round_rate = si5351_pll_round_rate,
> +       .set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +                                  int num, enum si5351_multisynth_src parent)
> +{
> +       if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +               return 0;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +                       (parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +                       SI5351_CLK_PLL_SELECT);
> +       return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +       return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +                              (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +                              SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = si5351_msynth_params_address(hwdata->num);
> +       unsigned long long rate;
> +       unsigned long m;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /*
> +        * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +        * multisync6-7: fOUT = fIN / P1
> +        */
> +       rate = parent_rate;
> +       if (hwdata->num > 5) {
> +               m = hwdata->params.p1;
> +       } else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +                   SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +               m = 4;
> +       } else {
> +               rate *= 128 * hwdata->params.p3;
> +               m = hwdata->params.p1 * hwdata->params.p3;
> +               m += hwdata->params.p2;
> +               m += 512 * hwdata->params.p3;
> +       }
> +
> +       if (m == 0)
> +               return 0;
> +       do_div(rate, m);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               m, parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long long lltmp;
> +       unsigned long a, b, c;
> +       int divby4;
> +
> +       /* multisync6-7 can only handle freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +       /* multisync frequency is 1MHz .. 160MHz */
> +       if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH_MAX_FREQ;
> +       if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +               rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +       divby4 = 0;
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* multisync can set pll */
> +       if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +               /*
> +                * find largest integer divider for max
> +                * vco frequency and given target rate
> +                */
> +               if (divby4 == 0) {
> +                       lltmp = SI5351_PLL_VCO_MAX;
> +                       do_div(lltmp, rate);
> +                       a = (unsigned long)lltmp;
> +               } else
> +                       a = 4;
> +
> +               b = 0;
> +               c = 1;
> +
> +               *parent_rate = a * rate;
> +       } else {
> +               unsigned long rfrac, denom;
> +
> +               /* disable divby4 */
> +               if (divby4) {
> +                       rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +                       divby4 = 0;
> +               }
> +
> +               /* determine integer part of divider equation */
> +               a = *parent_rate / rate;
> +               if (a < SI5351_MULTISYNTH_A_MIN)
> +                       a = SI5351_MULTISYNTH_A_MIN;
> +               if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +                       a = SI5351_MULTISYNTH67_A_MAX;
> +               else if (a > SI5351_MULTISYNTH_A_MAX)
> +                       a = SI5351_MULTISYNTH_A_MAX;
> +
> +               /* find best approximation for b/c = fVCO mod fOUT */
> +               denom = 1000 * 1000;
> +               lltmp = (*parent_rate) % rate;
> +               lltmp *= denom;
> +               do_div(lltmp, rate);
> +               rfrac = (unsigned long)lltmp;
> +
> +               b = 0;
> +               c = 1;
> +               if (rfrac)
> +                       rational_best_approximation(rfrac, denom,
> +                           SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +                           &b, &c);
> +       }
> +
> +       /* recalculate rate by fOUT = fIN / (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= c;
> +       do_div(lltmp, a * c + b);
> +       rate  = (unsigned long)lltmp;
> +
> +       /* calculate parameters */
> +       if (divby4) {
> +               hwdata->params.p3 = 1;
> +               hwdata->params.p2 = 0;
> +               hwdata->params.p1 = 0;
> +       } else {
> +               hwdata->params.p3  = c;
> +               hwdata->params.p2  = (128 * b) % c;
> +               hwdata->params.p1  = 128 * a;
> +               hwdata->params.p1 += (128 * b / c);
> +               hwdata->params.p1 -= 512;
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = si5351_msynth_params_address(hwdata->num);
> +       int divby4 = 0;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* enable/disable integer mode and divby4 on multisynth0-5 */
> +       if (hwdata->num < 6) {
> +               si5351_set_bits(hwdata->drvdata, reg + 2,
> +                               SI5351_OUTPUT_CLK_DIVBY4,
> +                               (divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INTEGER_MODE,
> +                       (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               divby4, parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +       .set_parent = si5351_msynth_set_parent,
> +       .get_parent = si5351_msynth_get_parent,
> +       .recalc_rate = si5351_msynth_recalc_rate,
> +       .round_rate = si5351_msynth_round_rate,
> +       .set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +                                  int num, enum si5351_clkout_src parent)
> +{
> +       u8 val;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       switch (parent) {
> +       case SI5351_CLKOUT_SRC_MSYNTH_N:
> +               val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               break;
> +       case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +               /* clk0/clk4 can only connect to its own multisync */
> +               if (num == 0 || num == 4)
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               else
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +               break;
> +       case SI5351_CLKOUT_SRC_XTAL:
> +               val = SI5351_CLK_INPUT_XTAL;
> +               break;
> +       case SI5351_CLKOUT_SRC_CLKIN:
> +               if (drvdata->variant != SI5351_VARIANT_C)
> +                       return -EINVAL;
> +
> +               val = SI5351_CLK_INPUT_CLKIN;
> +               break;
> +       default:
> +               return 0;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_INPUT_MASK, val);
> +       return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +       struct si5351_driver_data *drvdata, int num,
> +       enum si5351_drive_strength drive)
> +{
> +       u8 mask;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       switch (drive) {
> +       case SI5351_DRIVE_2MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +               break;
> +       case SI5351_DRIVE_4MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +               break;
> +       case SI5351_DRIVE_6MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +               break;
> +       case SI5351_DRIVE_8MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +               break;
> +       default:
> +               return 0;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +       return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), 0);
> +       return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       int index = 0;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +       switch (val & SI5351_CLK_INPUT_MASK) {
> +       case SI5351_CLK_INPUT_MULTISYNTH_N:
> +               index = 0;
> +               break;
> +       case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +               index = 1;
> +               break;
> +       case SI5351_CLK_INPUT_XTAL:
> +               index = 2;
> +               break;
> +       case SI5351_CLK_INPUT_CLKIN:
> +               index = 3;
> +               break;
> +       }
> +
> +       return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +       switch (index) {
> +       case 0:
> +               parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +               break;
> +       case 1:
> +               parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +               break;
> +       case 2:
> +               parent = SI5351_CLKOUT_SRC_XTAL;
> +               break;
> +       case 3:
> +               parent = SI5351_CLKOUT_SRC_CLKIN;
> +               break;
> +       }
> +
> +       return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg;
> +       unsigned char rdiv;
> +
> +       if (hwdata->num > 5)
> +               reg = si5351_msynth_params_address(hwdata->num) + 2;
> +       else
> +               reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +       rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +       if (hwdata->num == 6) {
> +               rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +       } else {
> +               rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +               rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +       }
> +
> +       return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char rdiv;
> +
> +       /* clkout6/7 can only handle output freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +               rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +       /* clkout freqency is 8kHz - 160MHz */
> +       if (rate > SI5351_CLKOUT_MAX_FREQ)
> +               rate = SI5351_CLKOUT_MAX_FREQ;
> +       if (rate < SI5351_CLKOUT_MIN_FREQ)
> +               rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +       /* request frequency if multisync master */
> +       if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +               /* use r divider for frequencies below 1MHz */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +                      rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +                       rdiv += 1;
> +                       rate *= 2;
> +               }
> +               *parent_rate = rate;
> +       } else {
> +               unsigned long new_rate, new_err, err;
> +
> +               /* round to closed rdiv */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               new_rate = *parent_rate;
> +               err = abs(new_rate - rate);
> +               do {
> +                       new_rate >>= 1;
> +                       new_err = abs(new_rate - rate);
> +                       if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                               break;
> +                       rdiv++;
> +                       err = new_err;
> +               } while (1);
> +       }
> +       rate = *parent_rate >> rdiv;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long new_rate, new_err, err;
> +       unsigned char rdiv;
> +
> +       /* round to closed rdiv */
> +       rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +       new_rate = parent_rate;
> +       err = abs(new_rate - rate);
> +       do {
> +               new_rate >>= 1;
> +               new_err = abs(new_rate - rate);
> +               if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                       break;
> +               rdiv++;
> +               err = new_err;
> +       } while (1);
> +
> +       /* write output divider */
> +       switch (hwdata->num) {
> +       case 6:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +               break;
> +       case 7:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +               break;
> +       default:
> +               si5351_set_bits(hwdata->drvdata,
> +                               si5351_msynth_params_address(hwdata->num) + 2,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +       }
> +
> +       /* powerup clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +       .prepare = si5351_clkout_prepare,
> +       .unprepare = si5351_clkout_unprepare,
> +       .set_parent = si5351_clkout_set_parent,
> +       .get_parent = si5351_clkout_get_parent,
> +       .recalc_rate = si5351_clkout_recalc_rate,
> +       .round_rate = si5351_clkout_round_rate,
> +       .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +       { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +       { .compatible = "silabs,si5351a-msop",
> +                                        .data = (void *)SI5351_VARIANT_A3, },
> +       { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +       { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +       struct device_node *child, *np = client->dev.of_node;
> +       struct si5351_platform_data *pdata;
> +       const struct of_device_id *match;
> +       struct property *prop;
> +       const __be32 *p;
> +       int num = 0;
> +       u32 val;
> +
> +       if (np == NULL)
> +               return 0;
> +
> +       match = of_match_node(si5351_dt_ids, np);
> +       if (match == NULL)
> +               return -EINVAL;
> +
> +       pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +       if (!pdata)
> +               return -ENOMEM;
> +
> +       pdata->variant = (enum si5351_variant)match->data;
> +       pdata->clk_xtal = of_clk_get(np, 0);
> +       if (!IS_ERR(pdata->clk_xtal))
> +               clk_put(pdata->clk_xtal);
> +       pdata->clk_clkin = of_clk_get(np, 1);
> +       if (!IS_ERR(pdata->clk_clkin))
> +               clk_put(pdata->clk_clkin);
> +
> +       /*
> +        * property silabs,pll-source : <num src>, [<..>]
> +        * allow to selectively set pll source
> +        */
> +       of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +               if (num >= 2) {
> +                       dev_err(&client->dev,
> +                               "invalid pll %d on pll-source prop\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               p = of_prop_next_u32(prop, p, &val);
> +               if (!p) {
> +                       dev_err(&client->dev,
> +                               "missing pll-source for pll %d\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               switch (val) {
> +               case 0:
> +                       pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +                       break;
> +               case 1:
> +                       if (pdata->variant != SI5351_VARIANT_C) {
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for pll %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +                       pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +                       break;
> +               default:
> +                       dev_err(&client->dev,
> +                                "invalid parent %d for pll %d\n", val, num);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       /* per clkout properties */
> +       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 >= 8 ||
> +                   (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +                       dev_err(&client->dev, "invalid clkout %d\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,multisynth-source",
> +                                         &val)) {
> +                       switch (val) {
> +                       case 0:
> +                               pdata->clkout[num].multisynth_src =
> +                                       SI5351_MULTISYNTH_SRC_VCO0;
> +                               break;
> +                       case 1:
> +                               pdata->clkout[num].multisynth_src =
> +                                       SI5351_MULTISYNTH_SRC_VCO1;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for multisynth %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +                       switch (val) {
> +                       case 0:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_MSYNTH_N;
> +                               break;
> +                       case 1:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +                               break;
> +                       case 2:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_XTAL;
> +                               break;
> +                       case 3:
> +                               if (pdata->variant != SI5351_VARIANT_C) {
> +                                       dev_err(&client->dev,
> +                                               "invalid parent %d for clkout %d\n",
> +                                               val, num);
> +                                       return -EINVAL;
> +                               }
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_CLKIN;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for clkout %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,drive-strength",
> +                                         &val)) {
> +                       switch (val) {
> +                       case SI5351_DRIVE_2MA:
> +                       case SI5351_DRIVE_4MA:
> +                       case SI5351_DRIVE_6MA:
> +                       case SI5351_DRIVE_8MA:
> +                               pdata->clkout[num].drive = val;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid drive strength %d for clkout %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "clock-frequency", &val))
> +                       pdata->clkout[num].rate = val;
> +
> +               pdata->clkout[num].pll_master =
> +                       of_property_read_bool(child, "silabs,pll-master");
> +       }
> +       client->dev.platform_data = pdata;
> +
> +       return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +       return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +                           const struct i2c_device_id *id)
> +{
> +       struct si5351_platform_data *pdata;
> +       struct si5351_driver_data *drvdata;
> +       struct clk_init_data init;
> +       struct clk *clk;
> +       const char *parent_names[4];
> +       u8 num_parents, num_clocks;
> +       int ret, n;
> +
> +       ret = si5351_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) {
> +               dev_err(&client->dev, "unable to allocate driver data\n");
> +               return -ENOMEM;
> +       }
> +
> +       i2c_set_clientdata(client, drvdata);
> +       drvdata->client = client;
> +       drvdata->variant = pdata->variant;
> +       drvdata->pxtal = pdata->clk_xtal;
> +       drvdata->pclkin = pdata->clk_clkin;
> +
> +       drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +       if (IS_ERR(drvdata->regmap)) {
> +               dev_err(&client->dev, "failed to allocate register map\n");
> +               return PTR_ERR(drvdata->regmap);
> +       }
> +
> +       /* Disable interrupts */
> +       si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +       /* Set disabled output drivers to drive low */
> +       si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +       si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +       /* Ensure pll select is on XTAL for Si5351A/B */
> +       if (drvdata->variant != SI5351_VARIANT_C)
> +               si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                               SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +       /* setup clock configuration */
> +       for (n = 0; n < 2; n++) {
> +               ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed to reparent pll %d to %d\n",
> +                               n, pdata->pll_src[n]);
> +                       return ret;
> +               }
> +       }
> +
> +       for (n = 0; n < 8; n++) {
> +               ret = _si5351_msynth_reparent(drvdata, n,
> +                                             pdata->clkout[n].multisynth_src);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed to reparent multisynth %d to %d\n",
> +                               n, pdata->clkout[n].multisynth_src);
> +                       return ret;
> +               }
> +
> +               ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +                                                       pdata->clkout[n].drive);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed set drive strength of clkout%d to %d\n",
> +                               n, pdata->clkout[n].drive);
> +                       return ret;
> +               }
> +       }
> +
> +       /* register xtal input clock gate */
> +       memset(&init, 0, sizeof(init));
> +       init.name = si5351_input_names[0];
> +       init.ops = &si5351_xtal_ops;
> +       init.flags = 0;
> +       if (!IS_ERR(drvdata->pxtal)) {
> +               drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +               init.parent_names = &drvdata->pxtal_name;
> +               init.num_parents = 1;
> +       }
> +       drvdata->xtal.init = &init;
> +       clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return PTR_ERR(clk);
> +       }
> +
> +       /* register clkin input clock gate */
> +       if (drvdata->variant == SI5351_VARIANT_C) {
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_input_names[1];
> +               init.ops = &si5351_clkin_ops;
> +               if (!IS_ERR(drvdata->pclkin)) {
> +                       drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +                       init.parent_names = &drvdata->pclkin_name;
> +                       init.num_parents = 1;
> +               }
> +               drvdata->clkin.init = &init;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return PTR_ERR(clk);
> +               }
> +       }
> +
> +       /* Si5351C allows to mux either xtal or clkin to PLL input */
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +       parent_names[0] = si5351_input_names[0];
> +       parent_names[1] = si5351_input_names[1];
> +
> +       /* register PLLA */
> +       drvdata->pll[0].num = 0;
> +       drvdata->pll[0].drvdata = drvdata;
> +       drvdata->pll[0].hw.init = &init;
> +       memset(&init, 0, sizeof(init));
> +       init.name = si5351_pll_names[0];
> +       init.ops = &si5351_pll_ops;
> +       init.flags = 0;
> +       init.parent_names = parent_names;
> +       init.num_parents = num_parents;
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register PLLB or VXCO (Si5351B) */
> +       drvdata->pll[1].num = 1;
> +       drvdata->pll[1].drvdata = drvdata;
> +       drvdata->pll[1].hw.init = &init;
> +       memset(&init, 0, sizeof(init));
> +       if (drvdata->variant == SI5351_VARIANT_B) {
> +               init.name = si5351_pll_names[2];
> +               init.ops = &si5351_vxco_ops;
> +               init.flags = CLK_IS_ROOT;
> +               init.parent_names = NULL;
> +               init.num_parents = 0;
> +       } else {
> +               init.name = si5351_pll_names[1];
> +               init.ops = &si5351_pll_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +       }
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register clk multisync and clk out divider */
> +       num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +       parent_names[0] = si5351_pll_names[0];
> +       if (drvdata->variant == SI5351_VARIANT_B)
> +               parent_names[1] = si5351_pll_names[2];
> +       else
> +               parent_names[1] = si5351_pll_names[1];
> +
> +       drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +                                      sizeof(*drvdata->msynth), GFP_KERNEL);
> +       drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +                                      sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +       drvdata->onecell.clk_num = num_clocks;
> +       drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +       if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +                   !drvdata->onecell.clks))
> +               return -ENOMEM;
> +
> +       for (n = 0; n < num_clocks; n++) {
> +               drvdata->msynth[n].num = n;
> +               drvdata->msynth[n].drvdata = drvdata;
> +               drvdata->msynth[n].hw.init = &init;
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_msynth_names[n];
> +               init.ops = &si5351_msynth_ops;
> +               init.flags = 0;
> +               if (pdata->clkout[n].pll_master)
> +                       init.flags |= CLK_SET_RATE_PARENT;
> +               init.parent_names = parent_names;
> +               init.num_parents = 2;
> +               clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +       parent_names[2] = si5351_input_names[0];
> +       parent_names[3] = si5351_input_names[1];
> +       for (n = 0; n < num_clocks; n++) {
> +               parent_names[0] = si5351_msynth_names[n];
> +               parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +                       si5351_msynth_names[4];
> +
> +               drvdata->clkout[n].num = n;
> +               drvdata->clkout[n].drvdata = drvdata;
> +               drvdata->clkout[n].hw.init = &init;
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_clkout_names[n];
> +               init.ops = &si5351_clkout_ops;
> +               init.flags = 0;
> +               if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +                       init.flags |= CLK_SET_RATE_PARENT;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +               drvdata->onecell.clks[n] = clk;
> +       }
> +
> +       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;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +       { "silabs,si5351", 0 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +       .driver = {
> +               .name = "si5351",
> +               .of_match_table = of_match_ptr(si5351_dt_ids),
> +       },
> +       .probe = si5351_i2c_probe,
> +       .id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR                   0x60
> +
> +#define SI5351_PLL_VCO_MIN                     600000000
> +#define SI5351_PLL_VCO_MAX                     900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ             1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ          150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ             160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ           SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ                 8000
> +#define SI5351_CLKOUT_MAX_FREQ                 SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ               SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN                       15
> +#define SI5351_PLL_A_MAX                       90
> +#define SI5351_PLL_B_MAX                       (SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX                       1048575
> +#define SI5351_MULTISYNTH_A_MIN                        6
> +#define SI5351_MULTISYNTH_A_MAX                        1800
> +#define SI5351_MULTISYNTH67_A_MAX              254
> +#define SI5351_MULTISYNTH_B_MAX                        (SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX                        1048575
> +#define SI5351_MULTISYNTH_P1_MAX               ((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX               ((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX               ((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS                   0
> +#define SI5351_INTERRUPT_STATUS                        1
> +#define SI5351_INTERRUPT_MASK                  2
> +#define  SI5351_STATUS_SYS_INIT                        (1<<7)
> +#define  SI5351_STATUS_LOL_B                   (1<<6)
> +#define  SI5351_STATUS_LOL_A                   (1<<5)
> +#define  SI5351_STATUS_LOS                     (1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL              3
> +#define SI5351_OEB_PIN_ENABLE_CTRL             9
> +#define SI5351_PLL_INPUT_SOURCE                        15
> +#define  SI5351_CLKIN_DIV_MASK                 (3<<6)
> +#define  SI5351_CLKIN_DIV_1                    (0<<6)
> +#define  SI5351_CLKIN_DIV_2                    (1<<6)
> +#define  SI5351_CLKIN_DIV_4                    (2<<6)
> +#define  SI5351_CLKIN_DIV_8                    (3<<6)
> +#define  SI5351_PLLB_SOURCE                    (1<<3)
> +#define  SI5351_PLLA_SOURCE                    (1<<2)
> +
> +#define SI5351_CLK0_CTRL                       16
> +#define SI5351_CLK1_CTRL                       17
> +#define SI5351_CLK2_CTRL                       18
> +#define SI5351_CLK3_CTRL                       19
> +#define SI5351_CLK4_CTRL                       20
> +#define SI5351_CLK5_CTRL                       21
> +#define SI5351_CLK6_CTRL                       22
> +#define SI5351_CLK7_CTRL                       23
> +#define  SI5351_CLK_POWERDOWN                  (1<<7)
> +#define  SI5351_CLK_INTEGER_MODE               (1<<6)
> +#define  SI5351_CLK_PLL_SELECT                 (1<<5)
> +#define  SI5351_CLK_INVERT                     (1<<4)
> +#define  SI5351_CLK_INPUT_MASK                 (3<<2)
> +#define  SI5351_CLK_INPUT_XTAL                 (0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN                        (1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4       (2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N         (3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK                (3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA         (0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA         (1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA         (2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA         (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE            24
> +#define SI5351_CLK7_4_DISABLE_STATE            25
> +#define  SI5351_CLK_DISABLE_STATE_LOW          0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH         1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT                2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER                3
> +
> +#define SI5351_PARAMETERS_LENGTH               8
> +#define SI5351_PLLA_PARAMETERS                 26
> +#define SI5351_PLLB_PARAMETERS                 34
> +#define SI5351_CLK0_PARAMETERS                 42
> +#define SI5351_CLK1_PARAMETERS                 50
> +#define SI5351_CLK2_PARAMETERS                 58
> +#define SI5351_CLK3_PARAMETERS                 66
> +#define SI5351_CLK4_PARAMETERS                 74
> +#define SI5351_CLK5_PARAMETERS                 82
> +#define SI5351_CLK6_PARAMETERS                 90
> +#define SI5351_CLK7_PARAMETERS                 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER           92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK            (7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK           (7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT           4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT          0
> +#define  SI5351_OUTPUT_CLK_DIV_1               0
> +#define  SI5351_OUTPUT_CLK_DIV_2               1
> +#define  SI5351_OUTPUT_CLK_DIV_4               2
> +#define  SI5351_OUTPUT_CLK_DIV_8               3
> +#define  SI5351_OUTPUT_CLK_DIV_16              4
> +#define  SI5351_OUTPUT_CLK_DIV_32              5
> +#define  SI5351_OUTPUT_CLK_DIV_64              6
> +#define  SI5351_OUTPUT_CLK_DIV_128             7
> +#define  SI5351_OUTPUT_CLK_DIVBY4              (3<<2)
> +
> +#define SI5351_SSC_PARAM0                      149
> +#define SI5351_SSC_PARAM1                      150
> +#define SI5351_SSC_PARAM2                      151
> +#define SI5351_SSC_PARAM3                      152
> +#define SI5351_SSC_PARAM4                      153
> +#define SI5351_SSC_PARAM5                      154
> +#define SI5351_SSC_PARAM6                      155
> +#define SI5351_SSC_PARAM7                      156
> +#define SI5351_SSC_PARAM8                      157
> +#define SI5351_SSC_PARAM9                      158
> +#define SI5351_SSC_PARAM10                     159
> +#define SI5351_SSC_PARAM11                     160
> +#define SI5351_SSC_PARAM12                     161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW             162
> +#define SI5351_VXCO_PARAMETERS_MID             163
> +#define SI5351_VXCO_PARAMETERS_HIGH            164
> +
> +#define SI5351_CLK0_PHASE_OFFSET               165
> +#define SI5351_CLK1_PHASE_OFFSET               166
> +#define SI5351_CLK2_PHASE_OFFSET               167
> +#define SI5351_CLK3_PHASE_OFFSET               168
> +#define SI5351_CLK4_PHASE_OFFSET               169
> +#define SI5351_CLK5_PHASE_OFFSET               170
> +
> +#define SI5351_PLL_RESET                       177
> +#define  SI5351_PLL_RESET_B                    (1<<7)
> +#define  SI5351_PLL_RESET_A                    (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD                    183
> +#define  SI5351_CRYSTAL_LOAD_MASK              (3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF               (1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF               (2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF              (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE                   187
> +#define  SI5351_CLKIN_ENABLE                   (1<<7)
> +#define  SI5351_XTAL_ENABLE                    (1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE              (1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +       SI5351_VARIANT_A = 1,
> +       SI5351_VARIANT_A3 = 2,
> +       SI5351_VARIANT_B = 3,
> +       SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +       SI5351_PLL_SRC_DEFAULT = 0,
> +       SI5351_PLL_SRC_XTAL = 1,
> +       SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +       SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +       SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +       SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +       SI5351_CLKOUT_SRC_DEFAULT = 0,
> +       SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +       SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +       SI5351_CLKOUT_SRC_XTAL = 3,
> +       SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +       SI5351_DRIVE_DEFAULT = 0,
> +       SI5351_DRIVE_2MA = 2,
> +       SI5351_DRIVE_4MA = 4,
> +       SI5351_DRIVE_6MA = 6,
> +       SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +       enum si5351_multisynth_src multisynth_src;
> +       enum si5351_clkout_src clkout_src;
> +       enum si5351_drive_strength drive;
> +       bool pll_master;
> +       unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +       enum si5351_variant variant;
> +       struct clk *clk_xtal;
> +       struct clk *clk_clkin;
> +       enum si5351_pll_src pll_src[2];
> +       struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> -- 
> 1.7.10.4

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v8] clk: add si5351 i2c common clock driver
@ 2013-04-12 18:19               ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-12 18:19 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Grant Likely, Rob Herring, Rob Landley, Stephen Warren,
	Thierry Reding, Dom Cobley, Linus Walleij, Arnd Bergmann,
	Andrew Morton, Pawel Moll, Mark Brown, Russell King - ARM Linux,
	Rabeeh Khoury, Daniel Mack, Jean-Francois Moine,
	Lars-Peter Clausen, Guenter Roeck, Michal Bachraty,
	devicetree-discuss, linux-doc, linux-kernel, linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-04-11 12:42:29)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Tested-by: Daniel Mack <zonque@gmail.com>
> Acked-by: Guenter Roeck <linux@roeck-us.net>

Made it under the wire!  Thanks for cooking up v8 so quickly.  I've
taken this into clk-next.  It's cool to have a clk driver for a discrete
clock generator device.

Regards,
Mike

> ---
> Changes from v7:
> - readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
> - check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
> - clean up i2c client init (Reported by Lars-Peter Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss@lists.ozlabs.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1510 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1904 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +       compatible = "fixed-clock";
> +       #clock-cells = <0>;
> +       clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +       /* Si5351a msop10 i2c clock generator */
> +       si5351a: clock-generator@60 {
> +               compatible = "silabs,si5351a-msop";
> +               reg = <0x60>;
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +               #clock-cells = <1>;
> +
> +               /* connect xtal input to 25MHz reference */
> +               clocks = <&ref25>;
> +
> +               /* connect xtal input as source of pll0 and pll1 */
> +               silabs,pll-source = <0 0>, <1 0>;
> +
> +               /*
> +                * overwrite clkout0 configuration with:
> +                * - 8mA output drive strength
> +                * - pll0 as clock source of multisynth0
> +                * - multisynth0 as clock source of output divider
> +                * - multisynth0 can change pll0
> +                * - set initial clock frequency of 74.25MHz
> +                */
> +               clkout0 {
> +                       reg = <0>;
> +                       silabs,drive-strength = <8>;
> +                       silabs,multisynth-source = <0>;
> +                       silabs,clock-source = <0>;
> +                       silabs,pll-master;
> +                       clock-frequency = <74250000>;
> +               };
> +
> +               /*
> +                * overwrite clkout1 configuration with:
> +                * - 4mA output drive strength
> +                * - pll1 as clock source of multisynth1
> +                * - multisynth1 as clock source of output divider
> +                * - multisynth1 can change pll1
> +                */
> +               clkout1 {
> +                       reg = <1>;
> +                       silabs,drive-strength = <4>;
> +                       silabs,multisynth-source = <1>;
> +                       silabs,clock-source = <0>;
> +                       pll-master;
> +               };
> +
> +               /*
> +                * overwrite clkout2 configuration with:
> +                * - xtal as clock source of output divider
> +                */
> +               clkout2 {
> +                       reg = <2>;
> +                       silabs,clock-source = <2>;
> +               };
> +       };
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung       Samsung Semiconductor
>  sbs    Smart Battery System
>  schindler      Schindler
>  sil    Silicon Image
> +silabs Silicon Laboratories
>  simtek
>  sirf   SiRF Technology, Inc.
>  snps   Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>         ---help---
>           This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +       tristate "Clock driver for SiLabs 5351A/B/C"
> +       depends on I2C
> +       select REGMAP_I2C
> +       select RATIONAL
> +       ---help---
> +         This driver supports Silicon Labs 5351A/B/C programmable clock
> +         generators.
> +
>  config CLK_TWL6040
>         tristate "External McPDM functional clock from twl6040"
>         depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)             += x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)      += clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..8927284
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1510 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +       unsigned long   p1;
> +       unsigned long   p2;
> +       unsigned long   p3;
> +       int             valid;
> +};
> +
> +struct si5351_hw_data {
> +       struct clk_hw                   hw;
> +       struct si5351_driver_data       *drvdata;
> +       struct si5351_parameters        params;
> +       unsigned char                   num;
> +};
> +
> +struct si5351_driver_data {
> +       enum si5351_variant     variant;
> +       struct i2c_client       *client;
> +       struct regmap           *regmap;
> +       struct clk_onecell_data onecell;
> +
> +       struct clk              *pxtal;
> +       const char              *pxtal_name;
> +       struct clk_hw           xtal;
> +       struct clk              *pclkin;
> +       const char              *pclkin_name;
> +       struct clk_hw           clkin;
> +
> +       struct si5351_hw_data   pll[2];
> +       struct si5351_hw_data   *msynth;
> +       struct si5351_hw_data   *clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +       "xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +       "plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +       "ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +       "clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
> +               return 0;
> +       }
> +
> +       return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +                                  u8 reg, u8 count, u8 *buf)
> +{
> +       return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +                                  u8 reg, u8 val)
> +{
> +       return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +                                   u8 reg, u8 count, const u8 *buf)
> +{
> +       return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +                                 u8 reg, u8 mask, u8 val)
> +{
> +       return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +       if (num > 5)
> +               return SI5351_CLK6_PARAMETERS + (num - 6);
> +       return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +                                  u8 reg, struct si5351_parameters *params)
> +{
> +       u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = si5351_reg_read(drvdata, reg);
> +               params->p1 = buf[0];
> +               params->p2 = 0;
> +               params->p3 = 1;
> +               break;
> +       default:
> +               si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +               params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +               params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +               params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +       }
> +       params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +                                   u8 reg, struct si5351_parameters *params)
> +{
> +       u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = params->p1 & 0xff;
> +               si5351_reg_write(drvdata, reg, buf[0]);
> +               break;
> +       default:
> +               buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +               buf[1] = params->p3 & 0xff;
> +               /* save rdiv and divby4 */
> +               buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +               buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +               buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +               buf[4] = params->p1 & 0xff;
> +               buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +                       ((params->p2 & 0xf0000) >> 16);
> +               buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +               buf[7] = params->p2 & 0xff;
> +               si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +       }
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +       switch (reg) {
> +       case SI5351_DEVICE_STATUS:
> +       case SI5351_INTERRUPT_STATUS:
> +       case SI5351_PLL_RESET:
> +               return true;
> +       }
> +       return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +       /* reserved registers */
> +       if (reg >= 4 && reg <= 8)
> +               return false;
> +       if (reg >= 10 && reg <= 14)
> +               return false;
> +       if (reg >= 173 && reg <= 176)
> +               return false;
> +       if (reg >= 178 && reg <= 182)
> +               return false;
> +       /* read-only */
> +       if (reg == SI5351_DEVICE_STATUS)
> +               return false;
> +       return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +       .reg_bits = 8,
> +       .val_bits = 8,
> +       .cache_type = REGCACHE_RBTREE,
> +       .max_register = 187,
> +       .writeable_reg = si5351_regmap_is_writeable,
> +       .volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +       .prepare = si5351_xtal_prepare,
> +       .unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +                                             unsigned long parent_rate)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       unsigned long rate;
> +       unsigned char idiv;
> +
> +       rate = parent_rate;
> +       if (parent_rate > 160000000) {
> +               idiv = SI5351_CLKIN_DIV_8;
> +               rate /= 8;
> +       } else if (parent_rate > 80000000) {
> +               idiv = SI5351_CLKIN_DIV_4;
> +               rate /= 4;
> +       } else if (parent_rate > 40000000) {
> +               idiv = SI5351_CLKIN_DIV_2;
> +               rate /= 2;
> +       } else {
> +               idiv = SI5351_CLKIN_DIV_1;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       SI5351_CLKIN_DIV_MASK, idiv);
> +
> +       dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +               __func__, (1 << (idiv >> 6)), rate);
> +
> +       return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +       .prepare = si5351_clkin_prepare,
> +       .unprepare = si5351_clkin_unprepare,
> +       .recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +       return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +                                            unsigned long parent_rate)
> +{
> +       return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long parent)
> +{
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +       .prepare = si5351_vxco_prepare,
> +       .unprepare = si5351_vxco_unprepare,
> +       .recalc_rate = si5351_vxco_recalc_rate,
> +       .set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +                               int num, enum si5351_pll_src parent)
> +{
> +       u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +       if (parent == SI5351_PLL_SRC_DEFAULT)
> +               return 0;
> +
> +       if (num > 2)
> +               return -EINVAL;
> +
> +       if (drvdata->variant != SI5351_VARIANT_C &&
> +           parent != SI5351_PLL_SRC_XTAL)
> +               return -EINVAL;
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +                       (parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +       return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +       u8 val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +       return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +           index > 0)
> +               return -EPERM;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +                            (index == 0) ? SI5351_PLL_SRC_XTAL :
> +                            SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +               SI5351_PLLB_PARAMETERS;
> +       unsigned long long rate;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +       rate  = hwdata->params.p1 * hwdata->params.p3;
> +       rate += 512 * hwdata->params.p3;
> +       rate += hwdata->params.p2;
> +       rate *= parent_rate;
> +       do_div(rate, 128 * hwdata->params.p3);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long rfrac, denom, a, b, c;
> +       unsigned long long lltmp;
> +
> +       if (rate < SI5351_PLL_VCO_MIN)
> +               rate = SI5351_PLL_VCO_MIN;
> +       if (rate > SI5351_PLL_VCO_MAX)
> +               rate = SI5351_PLL_VCO_MAX;
> +
> +       /* determine integer part of feedback equation */
> +       a = rate / *parent_rate;
> +
> +       if (a < SI5351_PLL_A_MIN)
> +               rate = *parent_rate * SI5351_PLL_A_MIN;
> +       if (a > SI5351_PLL_A_MAX)
> +               rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +       /* find best approximation for b/c = fVCO mod fIN */
> +       denom = 1000 * 1000;
> +       lltmp = rate % (*parent_rate);
> +       lltmp *= denom;
> +       do_div(lltmp, *parent_rate);
> +       rfrac = (unsigned long)lltmp;
> +
> +       b = 0;
> +       c = 1;
> +       if (rfrac)
> +               rational_best_approximation(rfrac, denom,
> +                                   SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +       /* calculate parameters */
> +       hwdata->params.p3  = c;
> +       hwdata->params.p2  = (128 * b) % c;
> +       hwdata->params.p1  = 128 * a;
> +       hwdata->params.p1 += (128 * b / c);
> +       hwdata->params.p1 -= 512;
> +
> +       /* recalculate rate by fIN * (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= b;
> +       do_div(lltmp, c);
> +
> +       rate  = (unsigned long)lltmp;
> +       rate += *parent_rate * a;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +               SI5351_PLLB_PARAMETERS;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       /* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +               SI5351_CLK_INTEGER_MODE,
> +               (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +       .set_parent = si5351_pll_set_parent,
> +       .get_parent = si5351_pll_get_parent,
> +       .recalc_rate = si5351_pll_recalc_rate,
> +       .round_rate = si5351_pll_round_rate,
> +       .set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +                                  int num, enum si5351_multisynth_src parent)
> +{
> +       if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +               return 0;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +                       (parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +                       SI5351_CLK_PLL_SELECT);
> +       return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +       return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +                              (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +                              SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = si5351_msynth_params_address(hwdata->num);
> +       unsigned long long rate;
> +       unsigned long m;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /*
> +        * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +        * multisync6-7: fOUT = fIN / P1
> +        */
> +       rate = parent_rate;
> +       if (hwdata->num > 5) {
> +               m = hwdata->params.p1;
> +       } else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +                   SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +               m = 4;
> +       } else {
> +               rate *= 128 * hwdata->params.p3;
> +               m = hwdata->params.p1 * hwdata->params.p3;
> +               m += hwdata->params.p2;
> +               m += 512 * hwdata->params.p3;
> +       }
> +
> +       if (m == 0)
> +               return 0;
> +       do_div(rate, m);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               m, parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long long lltmp;
> +       unsigned long a, b, c;
> +       int divby4;
> +
> +       /* multisync6-7 can only handle freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +       /* multisync frequency is 1MHz .. 160MHz */
> +       if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH_MAX_FREQ;
> +       if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +               rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +       divby4 = 0;
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* multisync can set pll */
> +       if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +               /*
> +                * find largest integer divider for max
> +                * vco frequency and given target rate
> +                */
> +               if (divby4 == 0) {
> +                       lltmp = SI5351_PLL_VCO_MAX;
> +                       do_div(lltmp, rate);
> +                       a = (unsigned long)lltmp;
> +               } else
> +                       a = 4;
> +
> +               b = 0;
> +               c = 1;
> +
> +               *parent_rate = a * rate;
> +       } else {
> +               unsigned long rfrac, denom;
> +
> +               /* disable divby4 */
> +               if (divby4) {
> +                       rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +                       divby4 = 0;
> +               }
> +
> +               /* determine integer part of divider equation */
> +               a = *parent_rate / rate;
> +               if (a < SI5351_MULTISYNTH_A_MIN)
> +                       a = SI5351_MULTISYNTH_A_MIN;
> +               if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +                       a = SI5351_MULTISYNTH67_A_MAX;
> +               else if (a > SI5351_MULTISYNTH_A_MAX)
> +                       a = SI5351_MULTISYNTH_A_MAX;
> +
> +               /* find best approximation for b/c = fVCO mod fOUT */
> +               denom = 1000 * 1000;
> +               lltmp = (*parent_rate) % rate;
> +               lltmp *= denom;
> +               do_div(lltmp, rate);
> +               rfrac = (unsigned long)lltmp;
> +
> +               b = 0;
> +               c = 1;
> +               if (rfrac)
> +                       rational_best_approximation(rfrac, denom,
> +                           SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +                           &b, &c);
> +       }
> +
> +       /* recalculate rate by fOUT = fIN / (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= c;
> +       do_div(lltmp, a * c + b);
> +       rate  = (unsigned long)lltmp;
> +
> +       /* calculate parameters */
> +       if (divby4) {
> +               hwdata->params.p3 = 1;
> +               hwdata->params.p2 = 0;
> +               hwdata->params.p1 = 0;
> +       } else {
> +               hwdata->params.p3  = c;
> +               hwdata->params.p2  = (128 * b) % c;
> +               hwdata->params.p1  = 128 * a;
> +               hwdata->params.p1 += (128 * b / c);
> +               hwdata->params.p1 -= 512;
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = si5351_msynth_params_address(hwdata->num);
> +       int divby4 = 0;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* enable/disable integer mode and divby4 on multisynth0-5 */
> +       if (hwdata->num < 6) {
> +               si5351_set_bits(hwdata->drvdata, reg + 2,
> +                               SI5351_OUTPUT_CLK_DIVBY4,
> +                               (divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INTEGER_MODE,
> +                       (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               divby4, parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +       .set_parent = si5351_msynth_set_parent,
> +       .get_parent = si5351_msynth_get_parent,
> +       .recalc_rate = si5351_msynth_recalc_rate,
> +       .round_rate = si5351_msynth_round_rate,
> +       .set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +                                  int num, enum si5351_clkout_src parent)
> +{
> +       u8 val;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       switch (parent) {
> +       case SI5351_CLKOUT_SRC_MSYNTH_N:
> +               val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               break;
> +       case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +               /* clk0/clk4 can only connect to its own multisync */
> +               if (num == 0 || num == 4)
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               else
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +               break;
> +       case SI5351_CLKOUT_SRC_XTAL:
> +               val = SI5351_CLK_INPUT_XTAL;
> +               break;
> +       case SI5351_CLKOUT_SRC_CLKIN:
> +               if (drvdata->variant != SI5351_VARIANT_C)
> +                       return -EINVAL;
> +
> +               val = SI5351_CLK_INPUT_CLKIN;
> +               break;
> +       default:
> +               return 0;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_INPUT_MASK, val);
> +       return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +       struct si5351_driver_data *drvdata, int num,
> +       enum si5351_drive_strength drive)
> +{
> +       u8 mask;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       switch (drive) {
> +       case SI5351_DRIVE_2MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +               break;
> +       case SI5351_DRIVE_4MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +               break;
> +       case SI5351_DRIVE_6MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +               break;
> +       case SI5351_DRIVE_8MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +               break;
> +       default:
> +               return 0;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +       return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), 0);
> +       return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       int index = 0;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +       switch (val & SI5351_CLK_INPUT_MASK) {
> +       case SI5351_CLK_INPUT_MULTISYNTH_N:
> +               index = 0;
> +               break;
> +       case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +               index = 1;
> +               break;
> +       case SI5351_CLK_INPUT_XTAL:
> +               index = 2;
> +               break;
> +       case SI5351_CLK_INPUT_CLKIN:
> +               index = 3;
> +               break;
> +       }
> +
> +       return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +       switch (index) {
> +       case 0:
> +               parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +               break;
> +       case 1:
> +               parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +               break;
> +       case 2:
> +               parent = SI5351_CLKOUT_SRC_XTAL;
> +               break;
> +       case 3:
> +               parent = SI5351_CLKOUT_SRC_CLKIN;
> +               break;
> +       }
> +
> +       return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg;
> +       unsigned char rdiv;
> +
> +       if (hwdata->num > 5)
> +               reg = si5351_msynth_params_address(hwdata->num) + 2;
> +       else
> +               reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +       rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +       if (hwdata->num == 6) {
> +               rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +       } else {
> +               rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +               rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +       }
> +
> +       return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char rdiv;
> +
> +       /* clkout6/7 can only handle output freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +               rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +       /* clkout freqency is 8kHz - 160MHz */
> +       if (rate > SI5351_CLKOUT_MAX_FREQ)
> +               rate = SI5351_CLKOUT_MAX_FREQ;
> +       if (rate < SI5351_CLKOUT_MIN_FREQ)
> +               rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +       /* request frequency if multisync master */
> +       if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +               /* use r divider for frequencies below 1MHz */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +                      rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +                       rdiv += 1;
> +                       rate *= 2;
> +               }
> +               *parent_rate = rate;
> +       } else {
> +               unsigned long new_rate, new_err, err;
> +
> +               /* round to closed rdiv */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               new_rate = *parent_rate;
> +               err = abs(new_rate - rate);
> +               do {
> +                       new_rate >>= 1;
> +                       new_err = abs(new_rate - rate);
> +                       if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                               break;
> +                       rdiv++;
> +                       err = new_err;
> +               } while (1);
> +       }
> +       rate = *parent_rate >> rdiv;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long new_rate, new_err, err;
> +       unsigned char rdiv;
> +
> +       /* round to closed rdiv */
> +       rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +       new_rate = parent_rate;
> +       err = abs(new_rate - rate);
> +       do {
> +               new_rate >>= 1;
> +               new_err = abs(new_rate - rate);
> +               if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                       break;
> +               rdiv++;
> +               err = new_err;
> +       } while (1);
> +
> +       /* write output divider */
> +       switch (hwdata->num) {
> +       case 6:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +               break;
> +       case 7:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +               break;
> +       default:
> +               si5351_set_bits(hwdata->drvdata,
> +                               si5351_msynth_params_address(hwdata->num) + 2,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +       }
> +
> +       /* powerup clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +       .prepare = si5351_clkout_prepare,
> +       .unprepare = si5351_clkout_unprepare,
> +       .set_parent = si5351_clkout_set_parent,
> +       .get_parent = si5351_clkout_get_parent,
> +       .recalc_rate = si5351_clkout_recalc_rate,
> +       .round_rate = si5351_clkout_round_rate,
> +       .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +       { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +       { .compatible = "silabs,si5351a-msop",
> +                                        .data = (void *)SI5351_VARIANT_A3, },
> +       { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +       { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +       struct device_node *child, *np = client->dev.of_node;
> +       struct si5351_platform_data *pdata;
> +       const struct of_device_id *match;
> +       struct property *prop;
> +       const __be32 *p;
> +       int num = 0;
> +       u32 val;
> +
> +       if (np == NULL)
> +               return 0;
> +
> +       match = of_match_node(si5351_dt_ids, np);
> +       if (match == NULL)
> +               return -EINVAL;
> +
> +       pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +       if (!pdata)
> +               return -ENOMEM;
> +
> +       pdata->variant = (enum si5351_variant)match->data;
> +       pdata->clk_xtal = of_clk_get(np, 0);
> +       if (!IS_ERR(pdata->clk_xtal))
> +               clk_put(pdata->clk_xtal);
> +       pdata->clk_clkin = of_clk_get(np, 1);
> +       if (!IS_ERR(pdata->clk_clkin))
> +               clk_put(pdata->clk_clkin);
> +
> +       /*
> +        * property silabs,pll-source : <num src>, [<..>]
> +        * allow to selectively set pll source
> +        */
> +       of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +               if (num >= 2) {
> +                       dev_err(&client->dev,
> +                               "invalid pll %d on pll-source prop\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               p = of_prop_next_u32(prop, p, &val);
> +               if (!p) {
> +                       dev_err(&client->dev,
> +                               "missing pll-source for pll %d\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               switch (val) {
> +               case 0:
> +                       pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +                       break;
> +               case 1:
> +                       if (pdata->variant != SI5351_VARIANT_C) {
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for pll %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +                       pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +                       break;
> +               default:
> +                       dev_err(&client->dev,
> +                                "invalid parent %d for pll %d\n", val, num);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       /* per clkout properties */
> +       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 >= 8 ||
> +                   (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +                       dev_err(&client->dev, "invalid clkout %d\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,multisynth-source",
> +                                         &val)) {
> +                       switch (val) {
> +                       case 0:
> +                               pdata->clkout[num].multisynth_src =
> +                                       SI5351_MULTISYNTH_SRC_VCO0;
> +                               break;
> +                       case 1:
> +                               pdata->clkout[num].multisynth_src =
> +                                       SI5351_MULTISYNTH_SRC_VCO1;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for multisynth %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +                       switch (val) {
> +                       case 0:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_MSYNTH_N;
> +                               break;
> +                       case 1:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +                               break;
> +                       case 2:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_XTAL;
> +                               break;
> +                       case 3:
> +                               if (pdata->variant != SI5351_VARIANT_C) {
> +                                       dev_err(&client->dev,
> +                                               "invalid parent %d for clkout %d\n",
> +                                               val, num);
> +                                       return -EINVAL;
> +                               }
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_CLKIN;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for clkout %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,drive-strength",
> +                                         &val)) {
> +                       switch (val) {
> +                       case SI5351_DRIVE_2MA:
> +                       case SI5351_DRIVE_4MA:
> +                       case SI5351_DRIVE_6MA:
> +                       case SI5351_DRIVE_8MA:
> +                               pdata->clkout[num].drive = val;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid drive strength %d for clkout %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "clock-frequency", &val))
> +                       pdata->clkout[num].rate = val;
> +
> +               pdata->clkout[num].pll_master =
> +                       of_property_read_bool(child, "silabs,pll-master");
> +       }
> +       client->dev.platform_data = pdata;
> +
> +       return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +       return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +                           const struct i2c_device_id *id)
> +{
> +       struct si5351_platform_data *pdata;
> +       struct si5351_driver_data *drvdata;
> +       struct clk_init_data init;
> +       struct clk *clk;
> +       const char *parent_names[4];
> +       u8 num_parents, num_clocks;
> +       int ret, n;
> +
> +       ret = si5351_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) {
> +               dev_err(&client->dev, "unable to allocate driver data\n");
> +               return -ENOMEM;
> +       }
> +
> +       i2c_set_clientdata(client, drvdata);
> +       drvdata->client = client;
> +       drvdata->variant = pdata->variant;
> +       drvdata->pxtal = pdata->clk_xtal;
> +       drvdata->pclkin = pdata->clk_clkin;
> +
> +       drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +       if (IS_ERR(drvdata->regmap)) {
> +               dev_err(&client->dev, "failed to allocate register map\n");
> +               return PTR_ERR(drvdata->regmap);
> +       }
> +
> +       /* Disable interrupts */
> +       si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +       /* Set disabled output drivers to drive low */
> +       si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +       si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +       /* Ensure pll select is on XTAL for Si5351A/B */
> +       if (drvdata->variant != SI5351_VARIANT_C)
> +               si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                               SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +       /* setup clock configuration */
> +       for (n = 0; n < 2; n++) {
> +               ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed to reparent pll %d to %d\n",
> +                               n, pdata->pll_src[n]);
> +                       return ret;
> +               }
> +       }
> +
> +       for (n = 0; n < 8; n++) {
> +               ret = _si5351_msynth_reparent(drvdata, n,
> +                                             pdata->clkout[n].multisynth_src);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed to reparent multisynth %d to %d\n",
> +                               n, pdata->clkout[n].multisynth_src);
> +                       return ret;
> +               }
> +
> +               ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +                                                       pdata->clkout[n].drive);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed set drive strength of clkout%d to %d\n",
> +                               n, pdata->clkout[n].drive);
> +                       return ret;
> +               }
> +       }
> +
> +       /* register xtal input clock gate */
> +       memset(&init, 0, sizeof(init));
> +       init.name = si5351_input_names[0];
> +       init.ops = &si5351_xtal_ops;
> +       init.flags = 0;
> +       if (!IS_ERR(drvdata->pxtal)) {
> +               drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +               init.parent_names = &drvdata->pxtal_name;
> +               init.num_parents = 1;
> +       }
> +       drvdata->xtal.init = &init;
> +       clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return PTR_ERR(clk);
> +       }
> +
> +       /* register clkin input clock gate */
> +       if (drvdata->variant == SI5351_VARIANT_C) {
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_input_names[1];
> +               init.ops = &si5351_clkin_ops;
> +               if (!IS_ERR(drvdata->pclkin)) {
> +                       drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +                       init.parent_names = &drvdata->pclkin_name;
> +                       init.num_parents = 1;
> +               }
> +               drvdata->clkin.init = &init;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return PTR_ERR(clk);
> +               }
> +       }
> +
> +       /* Si5351C allows to mux either xtal or clkin to PLL input */
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +       parent_names[0] = si5351_input_names[0];
> +       parent_names[1] = si5351_input_names[1];
> +
> +       /* register PLLA */
> +       drvdata->pll[0].num = 0;
> +       drvdata->pll[0].drvdata = drvdata;
> +       drvdata->pll[0].hw.init = &init;
> +       memset(&init, 0, sizeof(init));
> +       init.name = si5351_pll_names[0];
> +       init.ops = &si5351_pll_ops;
> +       init.flags = 0;
> +       init.parent_names = parent_names;
> +       init.num_parents = num_parents;
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register PLLB or VXCO (Si5351B) */
> +       drvdata->pll[1].num = 1;
> +       drvdata->pll[1].drvdata = drvdata;
> +       drvdata->pll[1].hw.init = &init;
> +       memset(&init, 0, sizeof(init));
> +       if (drvdata->variant == SI5351_VARIANT_B) {
> +               init.name = si5351_pll_names[2];
> +               init.ops = &si5351_vxco_ops;
> +               init.flags = CLK_IS_ROOT;
> +               init.parent_names = NULL;
> +               init.num_parents = 0;
> +       } else {
> +               init.name = si5351_pll_names[1];
> +               init.ops = &si5351_pll_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +       }
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register clk multisync and clk out divider */
> +       num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +       parent_names[0] = si5351_pll_names[0];
> +       if (drvdata->variant == SI5351_VARIANT_B)
> +               parent_names[1] = si5351_pll_names[2];
> +       else
> +               parent_names[1] = si5351_pll_names[1];
> +
> +       drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +                                      sizeof(*drvdata->msynth), GFP_KERNEL);
> +       drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +                                      sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +       drvdata->onecell.clk_num = num_clocks;
> +       drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +       if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +                   !drvdata->onecell.clks))
> +               return -ENOMEM;
> +
> +       for (n = 0; n < num_clocks; n++) {
> +               drvdata->msynth[n].num = n;
> +               drvdata->msynth[n].drvdata = drvdata;
> +               drvdata->msynth[n].hw.init = &init;
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_msynth_names[n];
> +               init.ops = &si5351_msynth_ops;
> +               init.flags = 0;
> +               if (pdata->clkout[n].pll_master)
> +                       init.flags |= CLK_SET_RATE_PARENT;
> +               init.parent_names = parent_names;
> +               init.num_parents = 2;
> +               clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +       parent_names[2] = si5351_input_names[0];
> +       parent_names[3] = si5351_input_names[1];
> +       for (n = 0; n < num_clocks; n++) {
> +               parent_names[0] = si5351_msynth_names[n];
> +               parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +                       si5351_msynth_names[4];
> +
> +               drvdata->clkout[n].num = n;
> +               drvdata->clkout[n].drvdata = drvdata;
> +               drvdata->clkout[n].hw.init = &init;
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_clkout_names[n];
> +               init.ops = &si5351_clkout_ops;
> +               init.flags = 0;
> +               if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +                       init.flags |= CLK_SET_RATE_PARENT;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +               drvdata->onecell.clks[n] = clk;
> +       }
> +
> +       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;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +       { "silabs,si5351", 0 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +       .driver = {
> +               .name = "si5351",
> +               .of_match_table = of_match_ptr(si5351_dt_ids),
> +       },
> +       .probe = si5351_i2c_probe,
> +       .id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR                   0x60
> +
> +#define SI5351_PLL_VCO_MIN                     600000000
> +#define SI5351_PLL_VCO_MAX                     900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ             1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ          150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ             160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ           SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ                 8000
> +#define SI5351_CLKOUT_MAX_FREQ                 SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ               SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN                       15
> +#define SI5351_PLL_A_MAX                       90
> +#define SI5351_PLL_B_MAX                       (SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX                       1048575
> +#define SI5351_MULTISYNTH_A_MIN                        6
> +#define SI5351_MULTISYNTH_A_MAX                        1800
> +#define SI5351_MULTISYNTH67_A_MAX              254
> +#define SI5351_MULTISYNTH_B_MAX                        (SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX                        1048575
> +#define SI5351_MULTISYNTH_P1_MAX               ((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX               ((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX               ((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS                   0
> +#define SI5351_INTERRUPT_STATUS                        1
> +#define SI5351_INTERRUPT_MASK                  2
> +#define  SI5351_STATUS_SYS_INIT                        (1<<7)
> +#define  SI5351_STATUS_LOL_B                   (1<<6)
> +#define  SI5351_STATUS_LOL_A                   (1<<5)
> +#define  SI5351_STATUS_LOS                     (1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL              3
> +#define SI5351_OEB_PIN_ENABLE_CTRL             9
> +#define SI5351_PLL_INPUT_SOURCE                        15
> +#define  SI5351_CLKIN_DIV_MASK                 (3<<6)
> +#define  SI5351_CLKIN_DIV_1                    (0<<6)
> +#define  SI5351_CLKIN_DIV_2                    (1<<6)
> +#define  SI5351_CLKIN_DIV_4                    (2<<6)
> +#define  SI5351_CLKIN_DIV_8                    (3<<6)
> +#define  SI5351_PLLB_SOURCE                    (1<<3)
> +#define  SI5351_PLLA_SOURCE                    (1<<2)
> +
> +#define SI5351_CLK0_CTRL                       16
> +#define SI5351_CLK1_CTRL                       17
> +#define SI5351_CLK2_CTRL                       18
> +#define SI5351_CLK3_CTRL                       19
> +#define SI5351_CLK4_CTRL                       20
> +#define SI5351_CLK5_CTRL                       21
> +#define SI5351_CLK6_CTRL                       22
> +#define SI5351_CLK7_CTRL                       23
> +#define  SI5351_CLK_POWERDOWN                  (1<<7)
> +#define  SI5351_CLK_INTEGER_MODE               (1<<6)
> +#define  SI5351_CLK_PLL_SELECT                 (1<<5)
> +#define  SI5351_CLK_INVERT                     (1<<4)
> +#define  SI5351_CLK_INPUT_MASK                 (3<<2)
> +#define  SI5351_CLK_INPUT_XTAL                 (0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN                        (1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4       (2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N         (3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK                (3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA         (0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA         (1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA         (2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA         (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE            24
> +#define SI5351_CLK7_4_DISABLE_STATE            25
> +#define  SI5351_CLK_DISABLE_STATE_LOW          0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH         1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT                2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER                3
> +
> +#define SI5351_PARAMETERS_LENGTH               8
> +#define SI5351_PLLA_PARAMETERS                 26
> +#define SI5351_PLLB_PARAMETERS                 34
> +#define SI5351_CLK0_PARAMETERS                 42
> +#define SI5351_CLK1_PARAMETERS                 50
> +#define SI5351_CLK2_PARAMETERS                 58
> +#define SI5351_CLK3_PARAMETERS                 66
> +#define SI5351_CLK4_PARAMETERS                 74
> +#define SI5351_CLK5_PARAMETERS                 82
> +#define SI5351_CLK6_PARAMETERS                 90
> +#define SI5351_CLK7_PARAMETERS                 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER           92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK            (7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK           (7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT           4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT          0
> +#define  SI5351_OUTPUT_CLK_DIV_1               0
> +#define  SI5351_OUTPUT_CLK_DIV_2               1
> +#define  SI5351_OUTPUT_CLK_DIV_4               2
> +#define  SI5351_OUTPUT_CLK_DIV_8               3
> +#define  SI5351_OUTPUT_CLK_DIV_16              4
> +#define  SI5351_OUTPUT_CLK_DIV_32              5
> +#define  SI5351_OUTPUT_CLK_DIV_64              6
> +#define  SI5351_OUTPUT_CLK_DIV_128             7
> +#define  SI5351_OUTPUT_CLK_DIVBY4              (3<<2)
> +
> +#define SI5351_SSC_PARAM0                      149
> +#define SI5351_SSC_PARAM1                      150
> +#define SI5351_SSC_PARAM2                      151
> +#define SI5351_SSC_PARAM3                      152
> +#define SI5351_SSC_PARAM4                      153
> +#define SI5351_SSC_PARAM5                      154
> +#define SI5351_SSC_PARAM6                      155
> +#define SI5351_SSC_PARAM7                      156
> +#define SI5351_SSC_PARAM8                      157
> +#define SI5351_SSC_PARAM9                      158
> +#define SI5351_SSC_PARAM10                     159
> +#define SI5351_SSC_PARAM11                     160
> +#define SI5351_SSC_PARAM12                     161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW             162
> +#define SI5351_VXCO_PARAMETERS_MID             163
> +#define SI5351_VXCO_PARAMETERS_HIGH            164
> +
> +#define SI5351_CLK0_PHASE_OFFSET               165
> +#define SI5351_CLK1_PHASE_OFFSET               166
> +#define SI5351_CLK2_PHASE_OFFSET               167
> +#define SI5351_CLK3_PHASE_OFFSET               168
> +#define SI5351_CLK4_PHASE_OFFSET               169
> +#define SI5351_CLK5_PHASE_OFFSET               170
> +
> +#define SI5351_PLL_RESET                       177
> +#define  SI5351_PLL_RESET_B                    (1<<7)
> +#define  SI5351_PLL_RESET_A                    (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD                    183
> +#define  SI5351_CRYSTAL_LOAD_MASK              (3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF               (1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF               (2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF              (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE                   187
> +#define  SI5351_CLKIN_ENABLE                   (1<<7)
> +#define  SI5351_XTAL_ENABLE                    (1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE              (1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +       SI5351_VARIANT_A = 1,
> +       SI5351_VARIANT_A3 = 2,
> +       SI5351_VARIANT_B = 3,
> +       SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +       SI5351_PLL_SRC_DEFAULT = 0,
> +       SI5351_PLL_SRC_XTAL = 1,
> +       SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +       SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +       SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +       SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +       SI5351_CLKOUT_SRC_DEFAULT = 0,
> +       SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +       SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +       SI5351_CLKOUT_SRC_XTAL = 3,
> +       SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +       SI5351_DRIVE_DEFAULT = 0,
> +       SI5351_DRIVE_2MA = 2,
> +       SI5351_DRIVE_4MA = 4,
> +       SI5351_DRIVE_6MA = 6,
> +       SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +       enum si5351_multisynth_src multisynth_src;
> +       enum si5351_clkout_src clkout_src;
> +       enum si5351_drive_strength drive;
> +       bool pll_master;
> +       unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +       enum si5351_variant variant;
> +       struct clk *clk_xtal;
> +       struct clk *clk_clkin;
> +       enum si5351_pll_src pll_src[2];
> +       struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> -- 
> 1.7.10.4

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v8] clk: add si5351 i2c common clock driver
@ 2013-04-12 18:19               ` Mike Turquette
  0 siblings, 0 replies; 95+ messages in thread
From: Mike Turquette @ 2013-04-12 18:19 UTC (permalink / raw)
  To: linux-arm-kernel

Quoting Sebastian Hesselbarth (2013-04-11 12:42:29)
> This patch adds a common clock driver for Silicon Labs Si5351a/b/c
> i2c programmable clock generators. Currently, the driver does not
> support VXCO feature of si5351b. Passing platform_data or DT bindings
> selectively allows to overwrite stored Si5351 configuration which is
> very helpful for clock generators with empty eeprom configuration.
> Corresponding device tree binding documentation is also added.
> 
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Tested-by: Daniel Mack <zonque@gmail.com>
> Acked-by: Guenter Roeck <linux@roeck-us.net>

Made it under the wire!  Thanks for cooking up v8 so quickly.  I've
taken this into clk-next.  It's cool to have a clk driver for a discrete
clock generator device.

Regards,
Mike

> ---
> Changes from v7:
> - readd clk powerup on clkout set_rate (Suggested by Michal Bachraty)
> 
> Changes from v6:
> - make invalid DT data parsing fatal (Suggested by Guenter Roeck)
> - add more variant checks and return errors
> - remove inline from _si5351_* functions
> - remove pll reset/clkout powerdown for gapless tuning
>   (Provided by Michal Backraty)
> 
> Changes from v5:
> - removed __clk_set_flags
> - remove CONFIG_OF dependency
> - introduce si5351_platform_data (non-DT untested)
> - parse DT into si5351_platform_data (tested)
> - minor cleanups
> 
> Changes from v4:
> - move from clk-private.h to clk-provider.h (Reported by Mike Turquette)
> - use __clk_set_flags() helper
> 
> Changes from v3:
> - add silabs prefix to custom DT properties (Reported by Lars-Peter Clausen)
> - use sizeof(*foo) instead of sizeof(struct bar) (Reported by Lars-Peter Clausen)
> - check return value of of_clk_add_provider (Reported by Lars-Peter Clausen)
> - clean up i2c client init (Reported by Lars-Peter Clausen)
> - silence successful probe (Suggested by Lars-Peter Clausen)
> - make CONFIG_CLK_SI5351 depend on CONFIG_OF
> 
> Changes from v2:
> - add curly brackets to if-else-statements (Reported by Daniel Mack)
> - fix div-by-zero for clk6/clk7 (Reported by Daniel Mack)
> - fix parameter address calculation for clk6/clk7
> 
> Changes from v1:
> - remove .is_enabled functions as they read from i2c
>   (Reported by Daniel Mack)
> - add CLK_SET_RATE_PARENT on clkout reparent if clkout uses
>   its own multisync
> 
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: Rob Herring <rob.herring@calxeda.com>
> Cc: Rob Landley <rob@landley.net>
> Cc: Mike Turquette <mturquette@linaro.org>
> Cc: Stephen Warren <swarren@nvidia.com>
> Cc: Thierry Reding <thierry.reding@avionic-design.de>
> Cc: Dom Cobley <popcornmix@gmail.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Pawel Moll <pawel.moll@arm.com>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
> Cc: Rabeeh Khoury <rabeeh@solid-run.com>
> Cc: Daniel Mack <zonque@gmail.com>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Michal Bachraty <michal.bachraty@streamunlimited.com>
> Cc: devicetree-discuss at lists.ozlabs.org
> Cc: linux-doc at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-arm-kernel at lists.infradead.org
> ---
>  .../devicetree/bindings/clock/silabs,si5351.txt    |  114 ++
>  .../devicetree/bindings/vendor-prefixes.txt        |    1 +
>  drivers/clk/Kconfig                                |    9 +
>  drivers/clk/Makefile                               |    1 +
>  drivers/clk/clk-si5351.c                           | 1510 ++++++++++++++++++++
>  drivers/clk/clk-si5351.h                           |  155 ++
>  include/linux/platform_data/si5351.h               |  114 ++
>  7 files changed, 1904 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt
>  create mode 100644 drivers/clk/clk-si5351.c
>  create mode 100644 drivers/clk/clk-si5351.h
>  create mode 100644 include/linux/platform_data/si5351.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> new file mode 100644
> index 0000000..cc37465
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt
> @@ -0,0 +1,114 @@
> +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator.
> +
> +Reference
> +[1] Si5351A/B/C Data Sheet
> +    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> +
> +The Si5351a/b/c are programmable i2c clock generators with upto 8 output
> +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only
> +3 output clocks are accessible. The internal structure of the clock
> +generators can be found in [1].
> +
> +==I2C device node==
> +
> +Required properties:
> +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}".
> +- reg: i2c device address, shall be 0x60 or 0x61.
> +- #clock-cells: from common clock binding; shall be set to 1.
> +- clocks: from common clock binding; list of parent clock
> +  handles, shall be xtal reference clock or xtal and clkin for
> +  si5351c only.
> +- #address-cells: shall be set to 1.
> +- #size-cells: shall be set to 0.
> +
> +Optional properties:
> +- silabs,pll-source: pair of (number, source) for each pll. Allows
> +  to overwrite clock source of pll A (number=0) or B (number=1).
> +
> +==Child nodes==
> +
> +Each of the clock outputs can be overwritten individually by
> +using a child node to the I2C device node. If a child node for a clock
> +output is not set, the eeprom configuration is not overwritten.
> +
> +Required child node properties:
> +- reg: number of clock output.
> +
> +Optional child node properties:
> +- silabs,clock-source: source clock of the output divider stage N, shall be
> +  0 = multisynth N
> +  1 = multisynth 0 for output clocks 0-3, else multisynth4
> +  2 = xtal
> +  3 = clkin (si5351c only)
> +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}.
> +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth
> +  divider.
> +- silabs,pll-master: boolean, multisynth can change pll frequency.
> +
> +==Example==
> +
> +/* 25MHz reference crystal */
> +ref25: ref25M {
> +       compatible = "fixed-clock";
> +       #clock-cells = <0>;
> +       clock-frequency = <25000000>;
> +};
> +
> +i2c-master-node {
> +
> +       /* Si5351a msop10 i2c clock generator */
> +       si5351a: clock-generator at 60 {
> +               compatible = "silabs,si5351a-msop";
> +               reg = <0x60>;
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +               #clock-cells = <1>;
> +
> +               /* connect xtal input to 25MHz reference */
> +               clocks = <&ref25>;
> +
> +               /* connect xtal input as source of pll0 and pll1 */
> +               silabs,pll-source = <0 0>, <1 0>;
> +
> +               /*
> +                * overwrite clkout0 configuration with:
> +                * - 8mA output drive strength
> +                * - pll0 as clock source of multisynth0
> +                * - multisynth0 as clock source of output divider
> +                * - multisynth0 can change pll0
> +                * - set initial clock frequency of 74.25MHz
> +                */
> +               clkout0 {
> +                       reg = <0>;
> +                       silabs,drive-strength = <8>;
> +                       silabs,multisynth-source = <0>;
> +                       silabs,clock-source = <0>;
> +                       silabs,pll-master;
> +                       clock-frequency = <74250000>;
> +               };
> +
> +               /*
> +                * overwrite clkout1 configuration with:
> +                * - 4mA output drive strength
> +                * - pll1 as clock source of multisynth1
> +                * - multisynth1 as clock source of output divider
> +                * - multisynth1 can change pll1
> +                */
> +               clkout1 {
> +                       reg = <1>;
> +                       silabs,drive-strength = <4>;
> +                       silabs,multisynth-source = <1>;
> +                       silabs,clock-source = <0>;
> +                       pll-master;
> +               };
> +
> +               /*
> +                * overwrite clkout2 configuration with:
> +                * - xtal as clock source of output divider
> +                */
> +               clkout2 {
> +                       reg = <2>;
> +                       silabs,clock-source = <2>;
> +               };
> +       };
> +};
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 19e1ef7..ca60849 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -48,6 +48,7 @@ samsung       Samsung Semiconductor
>  sbs    Smart Battery System
>  schindler      Schindler
>  sil    Silicon Image
> +silabs Silicon Laboratories
>  simtek
>  sirf   SiRF Technology, Inc.
>  snps   Synopsys, Inc.
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index a47e6ee..5039e41 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686
>         ---help---
>           This driver supports Maxim 77686 crystal oscillator clock. 
>  
> +config COMMON_CLK_SI5351
> +       tristate "Clock driver for SiLabs 5351A/B/C"
> +       depends on I2C
> +       select REGMAP_I2C
> +       select RATIONAL
> +       ---help---
> +         This driver supports Silicon Labs 5351A/B/C programmable clock
> +         generators.
> +
>  config CLK_TWL6040
>         tristate "External McPDM functional clock from twl6040"
>         depends on TWL6040_CORE
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 300d477..92ca698 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -33,4 +33,5 @@ obj-$(CONFIG_X86)             += x86/
>  # Chip specific
>  obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
> +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_CLK_TWL6040)      += clk-twl6040.o
> diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c
> new file mode 100644
> index 0000000..8927284
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.c
> @@ -0,0 +1,1510 @@
> +/*
> + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * References:
> + * [1] "Si5351A/B/C Data Sheet"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf
> + * [2] "Manually Generating an Si5351 Register Map"
> + *     http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
> + *
> + * 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 <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/rational.h>
> +#include <linux/i2c.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/si5351.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <asm/div64.h>
> +
> +#include "clk-si5351.h"
> +
> +struct si5351_driver_data;
> +
> +struct si5351_parameters {
> +       unsigned long   p1;
> +       unsigned long   p2;
> +       unsigned long   p3;
> +       int             valid;
> +};
> +
> +struct si5351_hw_data {
> +       struct clk_hw                   hw;
> +       struct si5351_driver_data       *drvdata;
> +       struct si5351_parameters        params;
> +       unsigned char                   num;
> +};
> +
> +struct si5351_driver_data {
> +       enum si5351_variant     variant;
> +       struct i2c_client       *client;
> +       struct regmap           *regmap;
> +       struct clk_onecell_data onecell;
> +
> +       struct clk              *pxtal;
> +       const char              *pxtal_name;
> +       struct clk_hw           xtal;
> +       struct clk              *pclkin;
> +       const char              *pclkin_name;
> +       struct clk_hw           clkin;
> +
> +       struct si5351_hw_data   pll[2];
> +       struct si5351_hw_data   *msynth;
> +       struct si5351_hw_data   *clkout;
> +};
> +
> +static const char const *si5351_input_names[] = {
> +       "xtal", "clkin"
> +};
> +static const char const *si5351_pll_names[] = {
> +       "plla", "pllb", "vxco"
> +};
> +static const char const *si5351_msynth_names[] = {
> +       "ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7"
> +};
> +static const char const *si5351_clkout_names[] = {
> +       "clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7"
> +};
> +
> +/*
> + * Si5351 i2c regmap
> + */
> +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 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\n", reg);
> +               return 0;
> +       }
> +
> +       return (u8)val;
> +}
> +
> +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata,
> +                                  u8 reg, u8 count, u8 *buf)
> +{
> +       return regmap_bulk_read(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_reg_write(struct si5351_driver_data *drvdata,
> +                                  u8 reg, u8 val)
> +{
> +       return regmap_write(drvdata->regmap, reg, val);
> +}
> +
> +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata,
> +                                   u8 reg, u8 count, const u8 *buf)
> +{
> +       return regmap_raw_write(drvdata->regmap, reg, buf, count);
> +}
> +
> +static inline int si5351_set_bits(struct si5351_driver_data *drvdata,
> +                                 u8 reg, u8 mask, u8 val)
> +{
> +       return regmap_update_bits(drvdata->regmap, reg, mask, val);
> +}
> +
> +static inline u8 si5351_msynth_params_address(int num)
> +{
> +       if (num > 5)
> +               return SI5351_CLK6_PARAMETERS + (num - 6);
> +       return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num);
> +}
> +
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> +                                  u8 reg, struct si5351_parameters *params)
> +{
> +       u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = si5351_reg_read(drvdata, reg);
> +               params->p1 = buf[0];
> +               params->p2 = 0;
> +               params->p3 = 1;
> +               break;
> +       default:
> +               si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +               params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4];
> +               params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7];
> +               params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1];
> +       }
> +       params->valid = 1;
> +}
> +
> +static void si5351_write_parameters(struct si5351_driver_data *drvdata,
> +                                   u8 reg, struct si5351_parameters *params)
> +{
> +       u8 buf[SI5351_PARAMETERS_LENGTH];
> +
> +       switch (reg) {
> +       case SI5351_CLK6_PARAMETERS:
> +       case SI5351_CLK7_PARAMETERS:
> +               buf[0] = params->p1 & 0xff;
> +               si5351_reg_write(drvdata, reg, buf[0]);
> +               break;
> +       default:
> +               buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff;
> +               buf[1] = params->p3 & 0xff;
> +               /* save rdiv and divby4 */
> +               buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03;
> +               buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03;
> +               buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff;
> +               buf[4] = params->p1 & 0xff;
> +               buf[5] = ((params->p3 & 0xf0000) >> 12) |
> +                       ((params->p2 & 0xf0000) >> 16);
> +               buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff;
> +               buf[7] = params->p2 & 0xff;
> +               si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf);
> +       }
> +}
> +
> +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg)
> +{
> +       switch (reg) {
> +       case SI5351_DEVICE_STATUS:
> +       case SI5351_INTERRUPT_STATUS:
> +       case SI5351_PLL_RESET:
> +               return true;
> +       }
> +       return false;
> +}
> +
> +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg)
> +{
> +       /* reserved registers */
> +       if (reg >= 4 && reg <= 8)
> +               return false;
> +       if (reg >= 10 && reg <= 14)
> +               return false;
> +       if (reg >= 173 && reg <= 176)
> +               return false;
> +       if (reg >= 178 && reg <= 182)
> +               return false;
> +       /* read-only */
> +       if (reg == SI5351_DEVICE_STATUS)
> +               return false;
> +       return true;
> +}
> +
> +static struct regmap_config si5351_regmap_config = {
> +       .reg_bits = 8,
> +       .val_bits = 8,
> +       .cache_type = REGCACHE_RBTREE,
> +       .max_register = 187,
> +       .writeable_reg = si5351_regmap_is_writeable,
> +       .volatile_reg = si5351_regmap_is_volatile,
> +};
> +
> +/*
> + * Si5351 xtal clock input
> + */
> +static int si5351_xtal_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_xtal_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, xtal);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_XTAL_ENABLE, 0);
> +}
> +
> +static const struct clk_ops si5351_xtal_ops = {
> +       .prepare = si5351_xtal_prepare,
> +       .unprepare = si5351_xtal_unprepare,
> +};
> +
> +/*
> + * Si5351 clkin clock input (Si5351C only)
> + */
> +static int si5351_clkin_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE);
> +       return 0;
> +}
> +
> +static void si5351_clkin_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE,
> +                       SI5351_CLKIN_ENABLE, 0);
> +}
> +
> +/*
> + * CMOS clock source constraints:
> + * The input frequency range of the PLL is 10Mhz to 40MHz.
> + * If CLKIN is >40MHz, the input divider must be used.
> + */
> +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw,
> +                                             unsigned long parent_rate)
> +{
> +       struct si5351_driver_data *drvdata =
> +               container_of(hw, struct si5351_driver_data, clkin);
> +       unsigned long rate;
> +       unsigned char idiv;
> +
> +       rate = parent_rate;
> +       if (parent_rate > 160000000) {
> +               idiv = SI5351_CLKIN_DIV_8;
> +               rate /= 8;
> +       } else if (parent_rate > 80000000) {
> +               idiv = SI5351_CLKIN_DIV_4;
> +               rate /= 4;
> +       } else if (parent_rate > 40000000) {
> +               idiv = SI5351_CLKIN_DIV_2;
> +               rate /= 2;
> +       } else {
> +               idiv = SI5351_CLKIN_DIV_1;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                       SI5351_CLKIN_DIV_MASK, idiv);
> +
> +       dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n",
> +               __func__, (1 << (idiv >> 6)), rate);
> +
> +       return rate;
> +}
> +
> +static const struct clk_ops si5351_clkin_ops = {
> +       .prepare = si5351_clkin_prepare,
> +       .unprepare = si5351_clkin_unprepare,
> +       .recalc_rate = si5351_clkin_recalc_rate,
> +};
> +
> +/*
> + * Si5351 vxco clock input (Si5351B only)
> + */
> +
> +static int si5351_vxco_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n");
> +
> +       return 0;
> +}
> +
> +static void si5351_vxco_unprepare(struct clk_hw *hw)
> +{
> +}
> +
> +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw,
> +                                            unsigned long parent_rate)
> +{
> +       return 0;
> +}
> +
> +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long parent)
> +{
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_vxco_ops = {
> +       .prepare = si5351_vxco_prepare,
> +       .unprepare = si5351_vxco_unprepare,
> +       .recalc_rate = si5351_vxco_recalc_rate,
> +       .set_rate = si5351_vxco_set_rate,
> +};
> +
> +/*
> + * Si5351 pll a/b
> + *
> + * Feedback Multisynth Divider Equations [2]
> + *
> + * fVCO = fIN * (a + b/c)
> + *
> + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and
> + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV
> + *
> + * Feedback Multisynth Register Equations
> + *
> + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * (3) MSNx_P3[19:0] = c
> + *
> + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c
> + *
> + * Using (4) on (1) yields:
> + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512
> + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c
> + *
> + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128
> + *         = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3)
> + *
> + */
> +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata,
> +                               int num, enum si5351_pll_src parent)
> +{
> +       u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +
> +       if (parent == SI5351_PLL_SRC_DEFAULT)
> +               return 0;
> +
> +       if (num > 2)
> +               return -EINVAL;
> +
> +       if (drvdata->variant != SI5351_VARIANT_C &&
> +           parent != SI5351_PLL_SRC_XTAL)
> +               return -EINVAL;
> +
> +       si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask,
> +                       (parent == SI5351_PLL_SRC_XTAL) ? 0 : mask);
> +       return 0;
> +}
> +
> +static unsigned char si5351_pll_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE;
> +       u8 val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE);
> +
> +       return (val & mask) ? 1 : 0;
> +}
> +
> +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       if (hwdata->drvdata->variant != SI5351_VARIANT_C &&
> +           index > 0)
> +               return -EPERM;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       return _si5351_pll_reparent(hwdata->drvdata, hwdata->num,
> +                            (index == 0) ? SI5351_PLL_SRC_XTAL :
> +                            SI5351_PLL_SRC_CLKIN);
> +}
> +
> +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +               SI5351_PLLB_PARAMETERS;
> +       unsigned long long rate;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */
> +       rate  = hwdata->params.p1 * hwdata->params.p3;
> +       rate += 512 * hwdata->params.p3;
> +       rate += hwdata->params.p2;
> +       rate *= parent_rate;
> +       do_div(rate, 128 * hwdata->params.p3);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long rfrac, denom, a, b, c;
> +       unsigned long long lltmp;
> +
> +       if (rate < SI5351_PLL_VCO_MIN)
> +               rate = SI5351_PLL_VCO_MIN;
> +       if (rate > SI5351_PLL_VCO_MAX)
> +               rate = SI5351_PLL_VCO_MAX;
> +
> +       /* determine integer part of feedback equation */
> +       a = rate / *parent_rate;
> +
> +       if (a < SI5351_PLL_A_MIN)
> +               rate = *parent_rate * SI5351_PLL_A_MIN;
> +       if (a > SI5351_PLL_A_MAX)
> +               rate = *parent_rate * SI5351_PLL_A_MAX;
> +
> +       /* find best approximation for b/c = fVCO mod fIN */
> +       denom = 1000 * 1000;
> +       lltmp = rate % (*parent_rate);
> +       lltmp *= denom;
> +       do_div(lltmp, *parent_rate);
> +       rfrac = (unsigned long)lltmp;
> +
> +       b = 0;
> +       c = 1;
> +       if (rfrac)
> +               rational_best_approximation(rfrac, denom,
> +                                   SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
> +
> +       /* calculate parameters */
> +       hwdata->params.p3  = c;
> +       hwdata->params.p2  = (128 * b) % c;
> +       hwdata->params.p1  = 128 * a;
> +       hwdata->params.p1 += (128 * b / c);
> +       hwdata->params.p1 -= 512;
> +
> +       /* recalculate rate by fIN * (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= b;
> +       do_div(lltmp, c);
> +
> +       rate  = (unsigned long)lltmp;
> +       rate += *parent_rate * a;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), a, b, c,
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS :
> +               SI5351_PLLB_PARAMETERS;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       /* plla/pllb ctrl is in clk6/clk7 ctrl registers */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num,
> +               SI5351_CLK_INTEGER_MODE,
> +               (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_pll_ops = {
> +       .set_parent = si5351_pll_set_parent,
> +       .get_parent = si5351_pll_get_parent,
> +       .recalc_rate = si5351_pll_recalc_rate,
> +       .round_rate = si5351_pll_round_rate,
> +       .set_rate = si5351_pll_set_rate,
> +};
> +
> +/*
> + * Si5351 multisync divider
> + *
> + * for fOUT <= 150 MHz:
> + *
> + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV
> + *
> + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and
> + * fIN = fVCO0, fVCO1
> + *
> + * Output Clock Multisynth Register Equations
> + *
> + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512
> + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c
> + * MSx_P3[19:0] = c
> + *
> + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0
> + *
> + * for 150MHz < fOUT <= 160MHz:
> + *
> + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b
> + */
> +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata,
> +                                  int num, enum si5351_multisynth_src parent)
> +{
> +       if (parent == SI5351_MULTISYNTH_SRC_DEFAULT)
> +               return 0;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT,
> +                       (parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 :
> +                       SI5351_CLK_PLL_SELECT);
> +       return 0;
> +}
> +
> +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +
> +       return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0;
> +}
> +
> +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num,
> +                              (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 :
> +                              SI5351_MULTISYNTH_SRC_VCO1);
> +}
> +
> +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = si5351_msynth_params_address(hwdata->num);
> +       unsigned long long rate;
> +       unsigned long m;
> +
> +       if (!hwdata->params.valid)
> +               si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (hwdata->params.p3 == 0)
> +               return parent_rate;
> +
> +       /*
> +        * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3)
> +        * multisync6-7: fOUT = fIN / P1
> +        */
> +       rate = parent_rate;
> +       if (hwdata->num > 5) {
> +               m = hwdata->params.p1;
> +       } else if ((si5351_reg_read(hwdata->drvdata, reg + 2) &
> +                   SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) {
> +               m = 4;
> +       } else {
> +               rate *= 128 * hwdata->params.p3;
> +               m = hwdata->params.p1 * hwdata->params.p3;
> +               m += hwdata->params.p2;
> +               m += 512 * hwdata->params.p3;
> +       }
> +
> +       if (m == 0)
> +               return 0;
> +       do_div(rate, m);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               m, parent_rate, (unsigned long)rate);
> +
> +       return (unsigned long)rate;
> +}
> +
> +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long long lltmp;
> +       unsigned long a, b, c;
> +       int divby4;
> +
> +       /* multisync6-7 can only handle freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH67_MAX_FREQ;
> +
> +       /* multisync frequency is 1MHz .. 160MHz */
> +       if (rate > SI5351_MULTISYNTH_MAX_FREQ)
> +               rate = SI5351_MULTISYNTH_MAX_FREQ;
> +       if (rate < SI5351_MULTISYNTH_MIN_FREQ)
> +               rate = SI5351_MULTISYNTH_MIN_FREQ;
> +
> +       divby4 = 0;
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* multisync can set pll */
> +       if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +               /*
> +                * find largest integer divider for max
> +                * vco frequency and given target rate
> +                */
> +               if (divby4 == 0) {
> +                       lltmp = SI5351_PLL_VCO_MAX;
> +                       do_div(lltmp, rate);
> +                       a = (unsigned long)lltmp;
> +               } else
> +                       a = 4;
> +
> +               b = 0;
> +               c = 1;
> +
> +               *parent_rate = a * rate;
> +       } else {
> +               unsigned long rfrac, denom;
> +
> +               /* disable divby4 */
> +               if (divby4) {
> +                       rate = SI5351_MULTISYNTH_DIVBY4_FREQ;
> +                       divby4 = 0;
> +               }
> +
> +               /* determine integer part of divider equation */
> +               a = *parent_rate / rate;
> +               if (a < SI5351_MULTISYNTH_A_MIN)
> +                       a = SI5351_MULTISYNTH_A_MIN;
> +               if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX)
> +                       a = SI5351_MULTISYNTH67_A_MAX;
> +               else if (a > SI5351_MULTISYNTH_A_MAX)
> +                       a = SI5351_MULTISYNTH_A_MAX;
> +
> +               /* find best approximation for b/c = fVCO mod fOUT */
> +               denom = 1000 * 1000;
> +               lltmp = (*parent_rate) % rate;
> +               lltmp *= denom;
> +               do_div(lltmp, rate);
> +               rfrac = (unsigned long)lltmp;
> +
> +               b = 0;
> +               c = 1;
> +               if (rfrac)
> +                       rational_best_approximation(rfrac, denom,
> +                           SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX,
> +                           &b, &c);
> +       }
> +
> +       /* recalculate rate by fOUT = fIN / (a + b/c) */
> +       lltmp  = *parent_rate;
> +       lltmp *= c;
> +       do_div(lltmp, a * c + b);
> +       rate  = (unsigned long)lltmp;
> +
> +       /* calculate parameters */
> +       if (divby4) {
> +               hwdata->params.p3 = 1;
> +               hwdata->params.p2 = 0;
> +               hwdata->params.p1 = 0;
> +       } else {
> +               hwdata->params.p3  = c;
> +               hwdata->params.p2  = (128 * b) % c;
> +               hwdata->params.p1  = 128 * a;
> +               hwdata->params.p1 += (128 * b / c);
> +               hwdata->params.p1 -= 512;
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4,
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       u8 reg = si5351_msynth_params_address(hwdata->num);
> +       int divby4 = 0;
> +
> +       /* write multisynth parameters */
> +       si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params);
> +
> +       if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ)
> +               divby4 = 1;
> +
> +       /* enable/disable integer mode and divby4 on multisynth0-5 */
> +       if (hwdata->num < 6) {
> +               si5351_set_bits(hwdata->drvdata, reg + 2,
> +                               SI5351_OUTPUT_CLK_DIVBY4,
> +                               (divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0);
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_INTEGER_MODE,
> +                       (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0);
> +       }
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk),
> +               hwdata->params.p1, hwdata->params.p2, hwdata->params.p3,
> +               divby4, parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_msynth_ops = {
> +       .set_parent = si5351_msynth_set_parent,
> +       .get_parent = si5351_msynth_get_parent,
> +       .recalc_rate = si5351_msynth_recalc_rate,
> +       .round_rate = si5351_msynth_round_rate,
> +       .set_rate = si5351_msynth_set_rate,
> +};
> +
> +/*
> + * Si5351 clkout divider
> + */
> +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> +                                  int num, enum si5351_clkout_src parent)
> +{
> +       u8 val;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       switch (parent) {
> +       case SI5351_CLKOUT_SRC_MSYNTH_N:
> +               val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               break;
> +       case SI5351_CLKOUT_SRC_MSYNTH_0_4:
> +               /* clk0/clk4 can only connect to its own multisync */
> +               if (num == 0 || num == 4)
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_N;
> +               else
> +                       val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> +               break;
> +       case SI5351_CLKOUT_SRC_XTAL:
> +               val = SI5351_CLK_INPUT_XTAL;
> +               break;
> +       case SI5351_CLKOUT_SRC_CLKIN:
> +               if (drvdata->variant != SI5351_VARIANT_C)
> +                       return -EINVAL;
> +
> +               val = SI5351_CLK_INPUT_CLKIN;
> +               break;
> +       default:
> +               return 0;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_INPUT_MASK, val);
> +       return 0;
> +}
> +
> +static int _si5351_clkout_set_drive_strength(
> +       struct si5351_driver_data *drvdata, int num,
> +       enum si5351_drive_strength drive)
> +{
> +       u8 mask;
> +
> +       if (num > 8)
> +               return -EINVAL;
> +
> +       switch (drive) {
> +       case SI5351_DRIVE_2MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_2MA;
> +               break;
> +       case SI5351_DRIVE_4MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_4MA;
> +               break;
> +       case SI5351_DRIVE_6MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_6MA;
> +               break;
> +       case SI5351_DRIVE_8MA:
> +               mask = SI5351_CLK_DRIVE_STRENGTH_8MA;
> +               break;
> +       default:
> +               return 0;
> +       }
> +
> +       si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num,
> +                       SI5351_CLK_DRIVE_STRENGTH_MASK, mask);
> +       return 0;
> +}
> +
> +static int si5351_clkout_prepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), 0);
> +       return 0;
> +}
> +
> +static void si5351_clkout_unprepare(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +       si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL,
> +                       (1 << hwdata->num), (1 << hwdata->num));
> +}
> +
> +static u8 si5351_clkout_get_parent(struct clk_hw *hw)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       int index = 0;
> +       unsigned char val;
> +
> +       val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num);
> +       switch (val & SI5351_CLK_INPUT_MASK) {
> +       case SI5351_CLK_INPUT_MULTISYNTH_N:
> +               index = 0;
> +               break;
> +       case SI5351_CLK_INPUT_MULTISYNTH_0_4:
> +               index = 1;
> +               break;
> +       case SI5351_CLK_INPUT_XTAL:
> +               index = 2;
> +               break;
> +       case SI5351_CLK_INPUT_CLKIN:
> +               index = 3;
> +               break;
> +       }
> +
> +       return index;
> +}
> +
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT;
> +
> +       switch (index) {
> +       case 0:
> +               parent = SI5351_CLKOUT_SRC_MSYNTH_N;
> +               break;
> +       case 1:
> +               parent = SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +               break;
> +       case 2:
> +               parent = SI5351_CLKOUT_SRC_XTAL;
> +               break;
> +       case 3:
> +               parent = SI5351_CLKOUT_SRC_CLKIN;
> +               break;
> +       }
> +
> +       return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent);
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char reg;
> +       unsigned char rdiv;
> +
> +       if (hwdata->num > 5)
> +               reg = si5351_msynth_params_address(hwdata->num) + 2;
> +       else
> +               reg = SI5351_CLK6_7_OUTPUT_DIVIDER;
> +
> +       rdiv = si5351_reg_read(hwdata->drvdata, reg);
> +       if (hwdata->num == 6) {
> +               rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK;
> +       } else {
> +               rdiv &= SI5351_OUTPUT_CLK_DIV_MASK;
> +               rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT;
> +       }
> +
> +       return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                    unsigned long *parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned char rdiv;
> +
> +       /* clkout6/7 can only handle output freqencies < 150MHz */
> +       if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> +               rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> +       /* clkout freqency is 8kHz - 160MHz */
> +       if (rate > SI5351_CLKOUT_MAX_FREQ)
> +               rate = SI5351_CLKOUT_MAX_FREQ;
> +       if (rate < SI5351_CLKOUT_MIN_FREQ)
> +               rate = SI5351_CLKOUT_MIN_FREQ;
> +
> +       /* request frequency if multisync master */
> +       if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) {
> +               /* use r divider for frequencies below 1MHz */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> +                      rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> +                       rdiv += 1;
> +                       rate *= 2;
> +               }
> +               *parent_rate = rate;
> +       } else {
> +               unsigned long new_rate, new_err, err;
> +
> +               /* round to closed rdiv */
> +               rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +               new_rate = *parent_rate;
> +               err = abs(new_rate - rate);
> +               do {
> +                       new_rate >>= 1;
> +                       new_err = abs(new_rate - rate);
> +                       if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                               break;
> +                       rdiv++;
> +                       err = new_err;
> +               } while (1);
> +       }
> +       rate = *parent_rate >> rdiv;
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +               *parent_rate, rate);
> +
> +       return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct si5351_hw_data *hwdata =
> +               container_of(hw, struct si5351_hw_data, hw);
> +       unsigned long new_rate, new_err, err;
> +       unsigned char rdiv;
> +
> +       /* round to closed rdiv */
> +       rdiv = SI5351_OUTPUT_CLK_DIV_1;
> +       new_rate = parent_rate;
> +       err = abs(new_rate - rate);
> +       do {
> +               new_rate >>= 1;
> +               new_err = abs(new_rate - rate);
> +               if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> +                       break;
> +               rdiv++;
> +               err = new_err;
> +       } while (1);
> +
> +       /* write output divider */
> +       switch (hwdata->num) {
> +       case 6:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> +               break;
> +       case 7:
> +               si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +               break;
> +       default:
> +               si5351_set_bits(hwdata->drvdata,
> +                               si5351_msynth_params_address(hwdata->num) + 2,
> +                               SI5351_OUTPUT_CLK_DIV_MASK,
> +                               rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> +       }
> +
> +       /* powerup clkout */
> +       si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> +                       SI5351_CLK_POWERDOWN, 0);
> +
> +       dev_dbg(&hwdata->drvdata->client->dev,
> +               "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> +               __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv),
> +               parent_rate, rate);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> +       .prepare = si5351_clkout_prepare,
> +       .unprepare = si5351_clkout_unprepare,
> +       .set_parent = si5351_clkout_set_parent,
> +       .get_parent = si5351_clkout_get_parent,
> +       .recalc_rate = si5351_clkout_recalc_rate,
> +       .round_rate = si5351_clkout_round_rate,
> +       .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +#ifdef CONFIG_OF
> +static const struct of_device_id si5351_dt_ids[] = {
> +       { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> +       { .compatible = "silabs,si5351a-msop",
> +                                        .data = (void *)SI5351_VARIANT_A3, },
> +       { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> +       { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, si5351_dt_ids);
> +
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +       struct device_node *child, *np = client->dev.of_node;
> +       struct si5351_platform_data *pdata;
> +       const struct of_device_id *match;
> +       struct property *prop;
> +       const __be32 *p;
> +       int num = 0;
> +       u32 val;
> +
> +       if (np == NULL)
> +               return 0;
> +
> +       match = of_match_node(si5351_dt_ids, np);
> +       if (match == NULL)
> +               return -EINVAL;
> +
> +       pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
> +       if (!pdata)
> +               return -ENOMEM;
> +
> +       pdata->variant = (enum si5351_variant)match->data;
> +       pdata->clk_xtal = of_clk_get(np, 0);
> +       if (!IS_ERR(pdata->clk_xtal))
> +               clk_put(pdata->clk_xtal);
> +       pdata->clk_clkin = of_clk_get(np, 1);
> +       if (!IS_ERR(pdata->clk_clkin))
> +               clk_put(pdata->clk_clkin);
> +
> +       /*
> +        * property silabs,pll-source : <num src>, [<..>]
> +        * allow to selectively set pll source
> +        */
> +       of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) {
> +               if (num >= 2) {
> +                       dev_err(&client->dev,
> +                               "invalid pll %d on pll-source prop\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               p = of_prop_next_u32(prop, p, &val);
> +               if (!p) {
> +                       dev_err(&client->dev,
> +                               "missing pll-source for pll %d\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               switch (val) {
> +               case 0:
> +                       pdata->pll_src[num] = SI5351_PLL_SRC_XTAL;
> +                       break;
> +               case 1:
> +                       if (pdata->variant != SI5351_VARIANT_C) {
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for pll %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +                       pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN;
> +                       break;
> +               default:
> +                       dev_err(&client->dev,
> +                                "invalid parent %d for pll %d\n", val, num);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       /* per clkout properties */
> +       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 >= 8 ||
> +                   (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) {
> +                       dev_err(&client->dev, "invalid clkout %d\n", num);
> +                       return -EINVAL;
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,multisynth-source",
> +                                         &val)) {
> +                       switch (val) {
> +                       case 0:
> +                               pdata->clkout[num].multisynth_src =
> +                                       SI5351_MULTISYNTH_SRC_VCO0;
> +                               break;
> +                       case 1:
> +                               pdata->clkout[num].multisynth_src =
> +                                       SI5351_MULTISYNTH_SRC_VCO1;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for multisynth %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
> +                       switch (val) {
> +                       case 0:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_MSYNTH_N;
> +                               break;
> +                       case 1:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_MSYNTH_0_4;
> +                               break;
> +                       case 2:
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_XTAL;
> +                               break;
> +                       case 3:
> +                               if (pdata->variant != SI5351_VARIANT_C) {
> +                                       dev_err(&client->dev,
> +                                               "invalid parent %d for clkout %d\n",
> +                                               val, num);
> +                                       return -EINVAL;
> +                               }
> +                               pdata->clkout[num].clkout_src =
> +                                       SI5351_CLKOUT_SRC_CLKIN;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid parent %d for clkout %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "silabs,drive-strength",
> +                                         &val)) {
> +                       switch (val) {
> +                       case SI5351_DRIVE_2MA:
> +                       case SI5351_DRIVE_4MA:
> +                       case SI5351_DRIVE_6MA:
> +                       case SI5351_DRIVE_8MA:
> +                               pdata->clkout[num].drive = val;
> +                               break;
> +                       default:
> +                               dev_err(&client->dev,
> +                                       "invalid drive strength %d for clkout %d\n",
> +                                       val, num);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               if (!of_property_read_u32(child, "clock-frequency", &val))
> +                       pdata->clkout[num].rate = val;
> +
> +               pdata->clkout[num].pll_master =
> +                       of_property_read_bool(child, "silabs,pll-master");
> +       }
> +       client->dev.platform_data = pdata;
> +
> +       return 0;
> +}
> +#else
> +static int si5351_dt_parse(struct i2c_client *client)
> +{
> +       return 0;
> +}
> +#endif /* CONFIG_OF */
> +
> +static int si5351_i2c_probe(struct i2c_client *client,
> +                           const struct i2c_device_id *id)
> +{
> +       struct si5351_platform_data *pdata;
> +       struct si5351_driver_data *drvdata;
> +       struct clk_init_data init;
> +       struct clk *clk;
> +       const char *parent_names[4];
> +       u8 num_parents, num_clocks;
> +       int ret, n;
> +
> +       ret = si5351_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) {
> +               dev_err(&client->dev, "unable to allocate driver data\n");
> +               return -ENOMEM;
> +       }
> +
> +       i2c_set_clientdata(client, drvdata);
> +       drvdata->client = client;
> +       drvdata->variant = pdata->variant;
> +       drvdata->pxtal = pdata->clk_xtal;
> +       drvdata->pclkin = pdata->clk_clkin;
> +
> +       drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> +       if (IS_ERR(drvdata->regmap)) {
> +               dev_err(&client->dev, "failed to allocate register map\n");
> +               return PTR_ERR(drvdata->regmap);
> +       }
> +
> +       /* Disable interrupts */
> +       si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> +       /* Set disabled output drivers to drive low */
> +       si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> +       si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> +       /* Ensure pll select is on XTAL for Si5351A/B */
> +       if (drvdata->variant != SI5351_VARIANT_C)
> +               si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> +                               SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> +       /* setup clock configuration */
> +       for (n = 0; n < 2; n++) {
> +               ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed to reparent pll %d to %d\n",
> +                               n, pdata->pll_src[n]);
> +                       return ret;
> +               }
> +       }
> +
> +       for (n = 0; n < 8; n++) {
> +               ret = _si5351_msynth_reparent(drvdata, n,
> +                                             pdata->clkout[n].multisynth_src);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed to reparent multisynth %d to %d\n",
> +                               n, pdata->clkout[n].multisynth_src);
> +                       return ret;
> +               }
> +
> +               ret = _si5351_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 = _si5351_clkout_set_drive_strength(drvdata, n,
> +                                                       pdata->clkout[n].drive);
> +               if (ret) {
> +                       dev_err(&client->dev,
> +                               "failed set drive strength of clkout%d to %d\n",
> +                               n, pdata->clkout[n].drive);
> +                       return ret;
> +               }
> +       }
> +
> +       /* register xtal input clock gate */
> +       memset(&init, 0, sizeof(init));
> +       init.name = si5351_input_names[0];
> +       init.ops = &si5351_xtal_ops;
> +       init.flags = 0;
> +       if (!IS_ERR(drvdata->pxtal)) {
> +               drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
> +               init.parent_names = &drvdata->pxtal_name;
> +               init.num_parents = 1;
> +       }
> +       drvdata->xtal.init = &init;
> +       clk = devm_clk_register(&client->dev, &drvdata->xtal);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return PTR_ERR(clk);
> +       }
> +
> +       /* register clkin input clock gate */
> +       if (drvdata->variant == SI5351_VARIANT_C) {
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_input_names[1];
> +               init.ops = &si5351_clkin_ops;
> +               if (!IS_ERR(drvdata->pclkin)) {
> +                       drvdata->pclkin_name = __clk_get_name(drvdata->pclkin);
> +                       init.parent_names = &drvdata->pclkin_name;
> +                       init.num_parents = 1;
> +               }
> +               drvdata->clkin.init = &init;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkin);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return PTR_ERR(clk);
> +               }
> +       }
> +
> +       /* Si5351C allows to mux either xtal or clkin to PLL input */
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> +       parent_names[0] = si5351_input_names[0];
> +       parent_names[1] = si5351_input_names[1];
> +
> +       /* register PLLA */
> +       drvdata->pll[0].num = 0;
> +       drvdata->pll[0].drvdata = drvdata;
> +       drvdata->pll[0].hw.init = &init;
> +       memset(&init, 0, sizeof(init));
> +       init.name = si5351_pll_names[0];
> +       init.ops = &si5351_pll_ops;
> +       init.flags = 0;
> +       init.parent_names = parent_names;
> +       init.num_parents = num_parents;
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register PLLB or VXCO (Si5351B) */
> +       drvdata->pll[1].num = 1;
> +       drvdata->pll[1].drvdata = drvdata;
> +       drvdata->pll[1].hw.init = &init;
> +       memset(&init, 0, sizeof(init));
> +       if (drvdata->variant == SI5351_VARIANT_B) {
> +               init.name = si5351_pll_names[2];
> +               init.ops = &si5351_vxco_ops;
> +               init.flags = CLK_IS_ROOT;
> +               init.parent_names = NULL;
> +               init.num_parents = 0;
> +       } else {
> +               init.name = si5351_pll_names[1];
> +               init.ops = &si5351_pll_ops;
> +               init.flags = 0;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +       }
> +       clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> +       if (IS_ERR(clk)) {
> +               dev_err(&client->dev, "unable to register %s\n", init.name);
> +               return -EINVAL;
> +       }
> +
> +       /* register clk multisync and clk out divider */
> +       num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> +       parent_names[0] = si5351_pll_names[0];
> +       if (drvdata->variant == SI5351_VARIANT_B)
> +               parent_names[1] = si5351_pll_names[2];
> +       else
> +               parent_names[1] = si5351_pll_names[1];
> +
> +       drvdata->msynth = devm_kzalloc(&client->dev, num_clocks *
> +                                      sizeof(*drvdata->msynth), GFP_KERNEL);
> +       drvdata->clkout = devm_kzalloc(&client->dev, num_clocks *
> +                                      sizeof(*drvdata->clkout), GFP_KERNEL);
> +
> +       drvdata->onecell.clk_num = num_clocks;
> +       drvdata->onecell.clks = devm_kzalloc(&client->dev,
> +               num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
> +
> +       if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> +                   !drvdata->onecell.clks))
> +               return -ENOMEM;
> +
> +       for (n = 0; n < num_clocks; n++) {
> +               drvdata->msynth[n].num = n;
> +               drvdata->msynth[n].drvdata = drvdata;
> +               drvdata->msynth[n].hw.init = &init;
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_msynth_names[n];
> +               init.ops = &si5351_msynth_ops;
> +               init.flags = 0;
> +               if (pdata->clkout[n].pll_master)
> +                       init.flags |= CLK_SET_RATE_PARENT;
> +               init.parent_names = parent_names;
> +               init.num_parents = 2;
> +               clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> +       parent_names[2] = si5351_input_names[0];
> +       parent_names[3] = si5351_input_names[1];
> +       for (n = 0; n < num_clocks; n++) {
> +               parent_names[0] = si5351_msynth_names[n];
> +               parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> +                       si5351_msynth_names[4];
> +
> +               drvdata->clkout[n].num = n;
> +               drvdata->clkout[n].drvdata = drvdata;
> +               drvdata->clkout[n].hw.init = &init;
> +               memset(&init, 0, sizeof(init));
> +               init.name = si5351_clkout_names[n];
> +               init.ops = &si5351_clkout_ops;
> +               init.flags = 0;
> +               if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N)
> +                       init.flags |= CLK_SET_RATE_PARENT;
> +               init.parent_names = parent_names;
> +               init.num_parents = num_parents;
> +               clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> +               if (IS_ERR(clk)) {
> +                       dev_err(&client->dev, "unable to register %s\n",
> +                               init.name);
> +                       return -EINVAL;
> +               }
> +               drvdata->onecell.clks[n] = clk;
> +       }
> +
> +       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;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> +       { "silabs,si5351", 0 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> +       .driver = {
> +               .name = "si5351",
> +               .of_match_table = of_match_ptr(si5351_dt_ids),
> +       },
> +       .probe = si5351_i2c_probe,
> +       .id_table = si5351_i2c_ids,
> +};
> +module_i2c_driver(si5351_driver);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..af41b50
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> + * Rabeeh Khoury <rabeeh@solid-run.com>
> + *
> + * 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_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR                   0x60
> +
> +#define SI5351_PLL_VCO_MIN                     600000000
> +#define SI5351_PLL_VCO_MAX                     900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ             1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ          150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ             160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ           SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ                 8000
> +#define SI5351_CLKOUT_MAX_FREQ                 SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ               SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN                       15
> +#define SI5351_PLL_A_MAX                       90
> +#define SI5351_PLL_B_MAX                       (SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX                       1048575
> +#define SI5351_MULTISYNTH_A_MIN                        6
> +#define SI5351_MULTISYNTH_A_MAX                        1800
> +#define SI5351_MULTISYNTH67_A_MAX              254
> +#define SI5351_MULTISYNTH_B_MAX                        (SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX                        1048575
> +#define SI5351_MULTISYNTH_P1_MAX               ((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX               ((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX               ((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS                   0
> +#define SI5351_INTERRUPT_STATUS                        1
> +#define SI5351_INTERRUPT_MASK                  2
> +#define  SI5351_STATUS_SYS_INIT                        (1<<7)
> +#define  SI5351_STATUS_LOL_B                   (1<<6)
> +#define  SI5351_STATUS_LOL_A                   (1<<5)
> +#define  SI5351_STATUS_LOS                     (1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL              3
> +#define SI5351_OEB_PIN_ENABLE_CTRL             9
> +#define SI5351_PLL_INPUT_SOURCE                        15
> +#define  SI5351_CLKIN_DIV_MASK                 (3<<6)
> +#define  SI5351_CLKIN_DIV_1                    (0<<6)
> +#define  SI5351_CLKIN_DIV_2                    (1<<6)
> +#define  SI5351_CLKIN_DIV_4                    (2<<6)
> +#define  SI5351_CLKIN_DIV_8                    (3<<6)
> +#define  SI5351_PLLB_SOURCE                    (1<<3)
> +#define  SI5351_PLLA_SOURCE                    (1<<2)
> +
> +#define SI5351_CLK0_CTRL                       16
> +#define SI5351_CLK1_CTRL                       17
> +#define SI5351_CLK2_CTRL                       18
> +#define SI5351_CLK3_CTRL                       19
> +#define SI5351_CLK4_CTRL                       20
> +#define SI5351_CLK5_CTRL                       21
> +#define SI5351_CLK6_CTRL                       22
> +#define SI5351_CLK7_CTRL                       23
> +#define  SI5351_CLK_POWERDOWN                  (1<<7)
> +#define  SI5351_CLK_INTEGER_MODE               (1<<6)
> +#define  SI5351_CLK_PLL_SELECT                 (1<<5)
> +#define  SI5351_CLK_INVERT                     (1<<4)
> +#define  SI5351_CLK_INPUT_MASK                 (3<<2)
> +#define  SI5351_CLK_INPUT_XTAL                 (0<<2)
> +#define  SI5351_CLK_INPUT_CLKIN                        (1<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_0_4       (2<<2)
> +#define  SI5351_CLK_INPUT_MULTISYNTH_N         (3<<2)
> +#define  SI5351_CLK_DRIVE_STRENGTH_MASK                (3<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_2MA         (0<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_4MA         (1<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_6MA         (2<<0)
> +#define  SI5351_CLK_DRIVE_STRENGTH_8MA         (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE            24
> +#define SI5351_CLK7_4_DISABLE_STATE            25
> +#define  SI5351_CLK_DISABLE_STATE_LOW          0
> +#define  SI5351_CLK_DISABLE_STATE_HIGH         1
> +#define  SI5351_CLK_DISABLE_STATE_FLOAT                2
> +#define  SI5351_CLK_DISABLE_STATE_NEVER                3
> +
> +#define SI5351_PARAMETERS_LENGTH               8
> +#define SI5351_PLLA_PARAMETERS                 26
> +#define SI5351_PLLB_PARAMETERS                 34
> +#define SI5351_CLK0_PARAMETERS                 42
> +#define SI5351_CLK1_PARAMETERS                 50
> +#define SI5351_CLK2_PARAMETERS                 58
> +#define SI5351_CLK3_PARAMETERS                 66
> +#define SI5351_CLK4_PARAMETERS                 74
> +#define SI5351_CLK5_PARAMETERS                 82
> +#define SI5351_CLK6_PARAMETERS                 90
> +#define SI5351_CLK7_PARAMETERS                 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER           92
> +#define  SI5351_OUTPUT_CLK_DIV_MASK            (7 << 4)
> +#define  SI5351_OUTPUT_CLK6_DIV_MASK           (7 << 0)
> +#define  SI5351_OUTPUT_CLK_DIV_SHIFT           4
> +#define  SI5351_OUTPUT_CLK_DIV6_SHIFT          0
> +#define  SI5351_OUTPUT_CLK_DIV_1               0
> +#define  SI5351_OUTPUT_CLK_DIV_2               1
> +#define  SI5351_OUTPUT_CLK_DIV_4               2
> +#define  SI5351_OUTPUT_CLK_DIV_8               3
> +#define  SI5351_OUTPUT_CLK_DIV_16              4
> +#define  SI5351_OUTPUT_CLK_DIV_32              5
> +#define  SI5351_OUTPUT_CLK_DIV_64              6
> +#define  SI5351_OUTPUT_CLK_DIV_128             7
> +#define  SI5351_OUTPUT_CLK_DIVBY4              (3<<2)
> +
> +#define SI5351_SSC_PARAM0                      149
> +#define SI5351_SSC_PARAM1                      150
> +#define SI5351_SSC_PARAM2                      151
> +#define SI5351_SSC_PARAM3                      152
> +#define SI5351_SSC_PARAM4                      153
> +#define SI5351_SSC_PARAM5                      154
> +#define SI5351_SSC_PARAM6                      155
> +#define SI5351_SSC_PARAM7                      156
> +#define SI5351_SSC_PARAM8                      157
> +#define SI5351_SSC_PARAM9                      158
> +#define SI5351_SSC_PARAM10                     159
> +#define SI5351_SSC_PARAM11                     160
> +#define SI5351_SSC_PARAM12                     161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW             162
> +#define SI5351_VXCO_PARAMETERS_MID             163
> +#define SI5351_VXCO_PARAMETERS_HIGH            164
> +
> +#define SI5351_CLK0_PHASE_OFFSET               165
> +#define SI5351_CLK1_PHASE_OFFSET               166
> +#define SI5351_CLK2_PHASE_OFFSET               167
> +#define SI5351_CLK3_PHASE_OFFSET               168
> +#define SI5351_CLK4_PHASE_OFFSET               169
> +#define SI5351_CLK5_PHASE_OFFSET               170
> +
> +#define SI5351_PLL_RESET                       177
> +#define  SI5351_PLL_RESET_B                    (1<<7)
> +#define  SI5351_PLL_RESET_A                    (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD                    183
> +#define  SI5351_CRYSTAL_LOAD_MASK              (3<<6)
> +#define  SI5351_CRYSTAL_LOAD_6PF               (1<<6)
> +#define  SI5351_CRYSTAL_LOAD_8PF               (2<<6)
> +#define  SI5351_CRYSTAL_LOAD_10PF              (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE                   187
> +#define  SI5351_CLKIN_ENABLE                   (1<<7)
> +#define  SI5351_XTAL_ENABLE                    (1<<6)
> +#define  SI5351_MULTISYNTH_ENABLE              (1<<4)
> +
> +#endif
> diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h
> new file mode 100644
> index 0000000..92dabca
> --- /dev/null
> +++ b/include/linux/platform_data/si5351.h
> @@ -0,0 +1,114 @@
> +/*
> + * Si5351A/B/C programmable clock generator platform_data.
> + */
> +
> +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__
> +#define __LINUX_PLATFORM_DATA_SI5351_H__
> +
> +struct clk;
> +
> +/**
> + * enum si5351_variant - SiLabs Si5351 chip variant
> + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
> + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
> + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
> + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
> + */
> +enum si5351_variant {
> +       SI5351_VARIANT_A = 1,
> +       SI5351_VARIANT_A3 = 2,
> +       SI5351_VARIANT_B = 3,
> +       SI5351_VARIANT_C = 4,
> +};
> +
> +/**
> + * enum si5351_pll_src - Si5351 pll clock source
> + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input
> + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only)
> + */
> +enum si5351_pll_src {
> +       SI5351_PLL_SRC_DEFAULT = 0,
> +       SI5351_PLL_SRC_XTAL = 1,
> +       SI5351_PLL_SRC_CLKIN = 2,
> +};
> +
> +/**
> + * enum si5351_multisynth_src - Si5351 multisynth clock source
> + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0
> + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO
> + */
> +enum si5351_multisynth_src {
> +       SI5351_MULTISYNTH_SRC_DEFAULT = 0,
> +       SI5351_MULTISYNTH_SRC_VCO0 = 1,
> +       SI5351_MULTISYNTH_SRC_VCO1 = 2,
> +};
> +
> +/**
> + * enum si5351_clkout_src - Si5351 clock output clock source
> + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config
> + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N
> + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4)
> + *                                or 4 (N>=4)
> + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL
> + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only)
> + */
> +enum si5351_clkout_src {
> +       SI5351_CLKOUT_SRC_DEFAULT = 0,
> +       SI5351_CLKOUT_SRC_MSYNTH_N = 1,
> +       SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2,
> +       SI5351_CLKOUT_SRC_XTAL = 3,
> +       SI5351_CLKOUT_SRC_CLKIN = 4,
> +};
> +
> +/**
> + * enum si5351_drive_strength - Si5351 clock output drive strength
> + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config
> + * @SI5351_DRIVE_2MA: 2mA clock output drive strength
> + * @SI5351_DRIVE_4MA: 4mA clock output drive strength
> + * @SI5351_DRIVE_6MA: 6mA clock output drive strength
> + * @SI5351_DRIVE_8MA: 8mA clock output drive strength
> + */
> +enum si5351_drive_strength {
> +       SI5351_DRIVE_DEFAULT = 0,
> +       SI5351_DRIVE_2MA = 2,
> +       SI5351_DRIVE_4MA = 4,
> +       SI5351_DRIVE_6MA = 6,
> +       SI5351_DRIVE_8MA = 8,
> +};
> +
> +/**
> + * struct si5351_clkout_config - Si5351 clock output configuration
> + * @clkout: clkout number
> + * @multisynth_src: multisynth source clock
> + * @clkout_src: clkout source clock
> + * @pll_master: if true, clkout can also change pll rate
> + * @drive: output drive strength
> + * @rate: initial clkout rate, or default if 0
> + */
> +struct si5351_clkout_config {
> +       enum si5351_multisynth_src multisynth_src;
> +       enum si5351_clkout_src clkout_src;
> +       enum si5351_drive_strength drive;
> +       bool pll_master;
> +       unsigned long rate;
> +};
> +
> +/**
> + * struct si5351_platform_data - Platform data for the Si5351 clock driver
> + * @variant: Si5351 chip variant
> + * @clk_xtal: xtal input clock
> + * @clk_clkin: clkin input clock
> + * @pll_src: array of pll source clock setting
> + * @clkout: array of clkout configuration
> + */
> +struct si5351_platform_data {
> +       enum si5351_variant variant;
> +       struct clk *clk_xtal;
> +       struct clk *clk_clkin;
> +       enum si5351_pll_src pll_src[2];
> +       struct si5351_clkout_config clkout[8];
> +};
> +
> +#endif
> -- 
> 1.7.10.4

^ permalink raw reply	[flat|nested] 95+ messages in thread

end of thread, other threads:[~2013-04-12 18:19 UTC | newest]

Thread overview: 95+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-02-09 12:59 [PATCH] clk: add si5351 i2c common clock driver Sebastian Hesselbarth
2013-02-09 12:59 ` Sebastian Hesselbarth
2013-02-11  5:46 ` Mike Turquette
2013-02-11  5:46   ` Mike Turquette
2013-02-11  5:46   ` Mike Turquette
2013-02-11  9:52   ` Sebastian Hesselbarth
2013-02-11  9:52     ` Sebastian Hesselbarth
2013-02-18 10:19 ` Daniel Mack
2013-02-18 10:19   ` Daniel Mack
2013-02-19 19:15 ` Daniel Mack
2013-02-19 19:15   ` Daniel Mack
2013-02-19 19:15   ` Daniel Mack
2013-02-27 10:01   ` Sebastian Hesselbarth
2013-02-27 10:01     ` Sebastian Hesselbarth
2013-03-01 15:01     ` Daniel Mack
2013-03-01 15:01       ` Daniel Mack
2013-03-16 13:10 ` [PATCH v2] " Sebastian Hesselbarth
2013-03-16 13:10   ` Sebastian Hesselbarth
2013-03-16 15:10   ` Daniel Mack
2013-03-16 15:10     ` Daniel Mack
2013-03-18 10:43 ` [PATCH v3] " Sebastian Hesselbarth
2013-03-18 10:43   ` Sebastian Hesselbarth
2013-03-18 11:37   ` Daniel Mack
2013-03-18 11:37     ` Daniel Mack
2013-03-20  0:26   ` Mike Turquette
2013-03-20  0:26     ` Mike Turquette
2013-03-20  0:26     ` Mike Turquette
2013-03-20  8:20     ` Sebastian Hesselbarth
2013-03-20  8:20       ` Sebastian Hesselbarth
2013-03-21 18:09   ` Lars-Peter Clausen
2013-03-21 18:09     ` Lars-Peter Clausen
2013-03-21 21:32     ` Sebastian Hesselbarth
2013-03-21 21:32       ` Sebastian Hesselbarth
2013-03-23 10:07       ` Lars-Peter Clausen
2013-03-23 10:07         ` Lars-Peter Clausen
2013-03-23 14:46   ` [PATCH v4] " Sebastian Hesselbarth
2013-03-23 14:46     ` Sebastian Hesselbarth
2013-03-23 14:46     ` Sebastian Hesselbarth
2013-04-02 23:46     ` Mike Turquette
2013-04-02 23:46       ` Mike Turquette
2013-04-02 23:46       ` Mike Turquette
2013-04-03 11:10       ` Sebastian Hesselbarth
2013-04-03 11:10         ` Sebastian Hesselbarth
2013-04-05  5:23     ` [PATCH v5] " Sebastian Hesselbarth
2013-04-05  5:23       ` Sebastian Hesselbarth
2013-04-07 22:50       ` [v5] " Guenter Roeck
2013-04-07 22:50         ` Guenter Roeck
2013-04-07 23:49         ` Sebastian Hesselbarth
2013-04-07 23:49           ` Sebastian Hesselbarth
2013-04-08  0:17           ` Guenter Roeck
2013-04-08  0:17             ` Guenter Roeck
2013-04-08  6:11             ` Sebastian Hesselbarth
2013-04-08  6:11               ` Sebastian Hesselbarth
2013-04-08 14:54               ` Guenter Roeck
2013-04-08 14:54                 ` Guenter Roeck
2013-04-08 14:54                 ` Guenter Roeck
2013-04-08 15:38                 ` Sebastian Hesselbarth
2013-04-08 15:38                   ` Sebastian Hesselbarth
2013-04-08 15:38                   ` Sebastian Hesselbarth
2013-04-08 17:36                   ` Mike Turquette
2013-04-08 17:36                     ` Mike Turquette
2013-04-08 18:32                     ` Sebastian Hesselbarth
2013-04-08 18:32                       ` Sebastian Hesselbarth
2013-04-08 16:46       ` [PATCH v6] " Sebastian Hesselbarth
2013-04-08 16:46         ` Sebastian Hesselbarth
2013-04-08 17:46         ` Guenter Roeck
2013-04-08 17:46           ` Guenter Roeck
2013-04-08 18:24           ` Sebastian Hesselbarth
2013-04-08 18:24             ` Sebastian Hesselbarth
2013-04-10  9:40         ` [PATCH v7] " Sebastian Hesselbarth
2013-04-10  9:40           ` Sebastian Hesselbarth
2013-04-10  9:40           ` Sebastian Hesselbarth
2013-04-10 10:17           ` Daniel Mack
2013-04-10 10:17             ` Daniel Mack
2013-04-10 14:48             ` Michal Bachraty
2013-04-10 14:48               ` Michal Bachraty
2013-04-10 16:34               ` Sebastian Hesselbarth
2013-04-10 17:27               ` Sebastian Hesselbarth
2013-04-10 17:27                 ` Sebastian Hesselbarth
2013-04-10 17:27                 ` Sebastian Hesselbarth
2013-04-11  7:44                 ` Michal Bachraty
2013-04-11  7:44                   ` Michal Bachraty
2013-04-11  8:22                   ` Sebastian Hesselbarth
2013-04-11  8:22                     ` Sebastian Hesselbarth
2013-04-10 19:34           ` Guenter Roeck
2013-04-10 19:34             ` Guenter Roeck
2013-04-11 19:42           ` [PATCH v8] " Sebastian Hesselbarth
2013-04-11 19:42             ` Sebastian Hesselbarth
2013-04-11 19:42             ` Sebastian Hesselbarth
2013-04-12 11:43             ` Michal Bachraty
2013-04-12 11:43               ` Michal Bachraty
2013-04-12 11:43               ` Michal Bachraty
2013-04-12 18:19             ` Mike Turquette
2013-04-12 18:19               ` Mike Turquette
2013-04-12 18:19               ` Mike Turquette

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.