2013-03-18 10:43:35

by Sebastian Hesselbarth

[permalink] [raw]
Subject: [PATCH v3] clk: add si5351 i2c common clock driver

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 <[email protected]>
---
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 <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Rob Landley <[email protected]>
Cc: Mike Turquette <[email protected]>
Cc: Stephen Warren <[email protected]>
Cc: Thierry Reding <[email protected]>
Cc: Dom Cobley <[email protected]>
Cc: Linus Walleij <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Russell King - ARM Linux <[email protected]>
Cc: Rabeeh Khoury <[email protected]>
Cc: Daniel Mack <[email protected]>
Cc: Jean-Francois Moine <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
---
.../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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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 <[email protected]");
+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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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


2013-03-18 11:38:06

by Daniel Mack

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

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 <[email protected]>

Tested-by: Daniel Mack <[email protected]>


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 <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Rob Landley <[email protected]>
> Cc: Mike Turquette <[email protected]>
> Cc: Stephen Warren <[email protected]>
> Cc: Thierry Reding <[email protected]>
> Cc: Dom Cobley <[email protected]>
> Cc: Linus Walleij <[email protected]>
> Cc: Arnd Bergmann <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Russell King - ARM Linux <[email protected]>
> Cc: Rabeeh Khoury <[email protected]>
> Cc: Daniel Mack <[email protected]>
> Cc: Jean-Francois Moine <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> ---
> .../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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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
>

2013-03-20 00:26:41

by Mike Turquette

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

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 <[email protected]>
> ---
> 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 <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Rob Landley <[email protected]>
> Cc: Mike Turquette <[email protected]>
> Cc: Stephen Warren <[email protected]>
> Cc: Thierry Reding <[email protected]>
> Cc: Dom Cobley <[email protected]>
> Cc: Linus Walleij <[email protected]>
> Cc: Arnd Bergmann <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Russell King - ARM Linux <[email protected]>
> Cc: Rabeeh Khoury <[email protected]>
> Cc: Daniel Mack <[email protected]>
> Cc: Jean-Francois Moine <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> ---
> .../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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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

2013-03-20 08:20:24

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

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<[email protected]>
>> ---
>> 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

2013-03-20 16:29:03

by michal.bachraty

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

Hi Sebastian,

Thanks for writing this driver!
I have tested your si5351 clock driver and his tuning capabilities. It works well, it generates proper clock frequency, but when new frequency is generated, little clock gap (1ms) is generated. Si5351 datasheet and WP claims, clock tuning can be without gaps - http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5350-51-Frequency-Shifting-WP.pdf
I made some tests with Si5351A chip and I found that when tuning touch only Multisynth registers, it can tune without gaps. There is no need for soft PLL reset. I found also, accessing Multisynth registers is not atomic, so there can be another frequency at output, while not all registers are written. Writing only to one register seems to be atomic.
I'm using this chip for master audio clock frequency generator and tuning about +-50ppm. The question is, if driver you made has capability for tuning without gaps in +-50pmm (or any other) range with about 1ppm step and if not, it is possible to add this functionality?

Thanks,

Michal

2013-03-20 16:48:43

by Daniel Mack

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

Hi Michal,

On 20.03.2013 14:55, [email protected] wrote:
> Thanks for writing this driver! I have tested your si5351 clock
> driver and his tuning capabilities. It works well, it generates
> proper clock frequency, but when new frequency is generated, little
> clock gap (1ms) is generated. Si5351 datasheet and WP claims, clock
> tuning can be without gaps -
> http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5350-51-Frequency-Shifting-WP.pdf
>
> I made some tests with Si5351A chip and I found that when tuning touch
> only Multisynth registers, it can tune without gaps. There is no need
> for soft PLL reset. I found also, accessing Multisynth registers is not
> atomic, so there can be another frequency at output, while not all
> registers are written. Writing only to one register seems to be atomic.

Yeah, but limiting possible changes to the PLLs to one single register
also means that you cannot offer to generate all the frequencies any
more. What could probably be done is refine the algorithm so that it
stays 'as close as possible' to the former values, but I'm not sure how
much work that implies.

Can you provide a patch against Sebastian's v3 to do that? Then it can
be cleanly applied on top of the driver later.


Daniel

2013-03-21 18:06:55

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

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 <[email protected]>

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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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

2013-03-21 21:32:55

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

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<[email protected]>
>
> 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<[email protected]");
>> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
>> +MODULE_LICENSE("GPL");
[...]

Again, thanks for the review.

Sebastian

2013-03-23 10:05:26

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

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

2013-03-23 14:47:12

by Sebastian Hesselbarth

[permalink] [raw]
Subject: [PATCH v4] clk: add si5351 i2c common clock driver

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 <[email protected]>
Tested-by: Daniel Mack <[email protected]>
---
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 <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Rob Landley <[email protected]>
Cc: Mike Turquette <[email protected]>
Cc: Stephen Warren <[email protected]>
Cc: Thierry Reding <[email protected]>
Cc: Dom Cobley <[email protected]>
Cc: Linus Walleij <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Russell King - ARM Linux <[email protected]>
Cc: Rabeeh Khoury <[email protected]>
Cc: Daniel Mack <[email protected]>
Cc: Jean-Francois Moine <[email protected]>
Cc: Lars-Peter Clausen <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
---
.../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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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 <[email protected]");
+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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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

2013-03-25 11:54:19

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [PATCH v3] clk: add si5351 i2c common clock driver

On Wed, Mar 20, 2013 at 5:48 PM, Daniel Mack <[email protected]> wrote:
> On 20.03.2013 14:55, [email protected] wrote:
>> Thanks for writing this driver! I have tested your si5351 clock
>> driver and his tuning capabilities. It works well, it generates
>> proper clock frequency, but when new frequency is generated, little
>> clock gap (1ms) is generated. Si5351 datasheet and WP claims, clock
>> tuning can be without gaps -
>> http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5350-51-Frequency-Shifting-WP.pdf
>>
>> I made some tests with Si5351A chip and I found that when tuning touch
>> only Multisynth registers, it can tune without gaps. There is no need
>> for soft PLL reset. I found also, accessing Multisynth registers is not
>> atomic, so there can be another frequency at output, while not all
>> registers are written. Writing only to one register seems to be atomic.

Michael,

if you don't configure the clock output to modify the pll, changing output
frequency will not alter pll config and there will be no reset of pll.

> Yeah, but limiting possible changes to the PLLs to one single register
> also means that you cannot offer to generate all the frequencies any
> more. What could probably be done is refine the algorithm so that it
> stays 'as close as possible' to the former values, but I'm not sure how
> much work that implies.
>
> Can you provide a patch against Sebastian's v3 to do that? Then it can
> be cleanly applied on top of the driver later.

Ack. Feel free to post a patch on top of v4 now.

Sebastian

2013-04-02 23:46:58

by Mike Turquette

[permalink] [raw]
Subject: Re: [PATCH v4] clk: add si5351 i2c common clock driver

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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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

2013-04-03 11:10:54

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [PATCH v4] clk: add si5351 i2c common clock driver

On Wed, Apr 3, 2013 at 1:46 AM, Mike Turquette <[email protected]> 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 <[email protected]>
>> + * Rabeeh Khoury <[email protected]>
>> + *
>> + * 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

2013-04-05 05:23:52

by Sebastian Hesselbarth

[permalink] [raw]
Subject: [PATCH v5] clk: add si5351 i2c common clock driver

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 <[email protected]>
Tested-by: Daniel Mack <[email protected]>
---
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 <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Rob Landley <[email protected]>
Cc: Mike Turquette <[email protected]>
Cc: Stephen Warren <[email protected]>
Cc: Thierry Reding <[email protected]>
Cc: Dom Cobley <[email protected]>
Cc: Linus Walleij <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Russell King - ARM Linux <[email protected]>
Cc: Rabeeh Khoury <[email protected]>
Cc: Daniel Mack <[email protected]>
Cc: Jean-Francois Moine <[email protected]>
Cc: Lars-Peter Clausen <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
---
.../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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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 <[email protected]");
+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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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

2013-04-07 22:50:44

by Guenter Roeck

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

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 <[email protected]>
> Tested-by: Daniel Mack <[email protected]>
>
[ ... ]

> +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

2013-04-07 23:49:31

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

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<[email protected]>
>> Tested-by: Daniel Mack<[email protected]>
>>
> [ ... ]
>
>> +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

2013-04-08 00:17:23

by Guenter Roeck

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

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<[email protected]>
> >>Tested-by: Daniel Mack<[email protected]>
> >>
> >[ ... ]
> >
> >>+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

2013-04-08 06:11:45

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

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<[email protected]>
>>>> Tested-by: Daniel Mack<[email protected]>
>>>>
>>> [ ... ]
>>>
>>>> +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

2013-04-08 14:54:05

by Guenter Roeck

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

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<[email protected]>
> >>>>Tested-by: Daniel Mack<[email protected]>
> >>>>
> >>>[ ... ]
> >>>
> >>>>+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

2013-04-08 15:38:48

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck <[email protected]> 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<[email protected]>
>> >>>>Tested-by: Daniel Mack<[email protected]>
>> >>>>
>> >>>[ ... ]
>> >>>
>> >>>>+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

2013-04-08 16:47:19

by Sebastian Hesselbarth

[permalink] [raw]
Subject: [PATCH v6] clk: add si5351 i2c common clock driver

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 <[email protected]>
---
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 <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Rob Landley <[email protected]>
Cc: Mike Turquette <[email protected]>
Cc: Stephen Warren <[email protected]>
Cc: Thierry Reding <[email protected]>
Cc: Dom Cobley <[email protected]>
Cc: Linus Walleij <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Russell King - ARM Linux <[email protected]>
Cc: Rabeeh Khoury <[email protected]>
Cc: Daniel Mack <[email protected]>
Cc: Jean-Francois Moine <[email protected]>
Cc: Lars-Peter Clausen <[email protected]>
Cc: Guenter Roeck <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
---
.../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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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 <[email protected]");
+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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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

2013-04-08 17:37:12

by Mike Turquette

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

Quoting Sebastian Hesselbarth (2013-04-08 08:38:44)
> On Mon, Apr 8, 2013 at 4:54 PM, Guenter Roeck <[email protected]> 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<[email protected]>
> >> >>>>Tested-by: Daniel Mack<[email protected]>
> >> >>>>
> >> >>>[ ... ]
> >> >>>
> >> >>>>+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

2013-04-08 17:46:33

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH v6] clk: add si5351 i2c common clock driver

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 <[email protected]>

[ ... ]

> +
> +/*
> + * 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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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
>
>

2013-04-08 18:24:16

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [PATCH v6] clk: add si5351 i2c common clock driver

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<[email protected]>
>
> [ ... ]
>
>> +
>> +/*
>> + * 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

2013-04-08 18:32:44

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [v5] clk: add si5351 i2c common clock driver

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<[email protected]> 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<[email protected]>
>>>>>>>> Tested-by: Daniel Mack<[email protected]>
>>>>>>>>
>>>>>>> [ ... ]
>>>>>>>
>>>>>>>> +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

2013-04-10 09:41:03

by Sebastian Hesselbarth

[permalink] [raw]
Subject: [PATCH v7] clk: add si5351 i2c common clock driver

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 <[email protected]>
---
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 <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Rob Landley <[email protected]>
Cc: Mike Turquette <[email protected]>
Cc: Stephen Warren <[email protected]>
Cc: Thierry Reding <[email protected]>
Cc: Dom Cobley <[email protected]>
Cc: Linus Walleij <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Russell King - ARM Linux <[email protected]>
Cc: Rabeeh Khoury <[email protected]>
Cc: Daniel Mack <[email protected]>
Cc: Jean-Francois Moine <[email protected]>
Cc: Lars-Peter Clausen <[email protected]>
Cc: Guenter Roeck <[email protected]>
Cc: Michal Bachraty <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
---
.../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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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 <[email protected]");
+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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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

2013-04-10 10:17:32

by Daniel Mack

[permalink] [raw]
Subject: Re: [PATCH v7] clk: add si5351 i2c common clock driver

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 <[email protected]>
> ---
> 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 <[email protected]>


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 <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Rob Landley <[email protected]>
> Cc: Mike Turquette <[email protected]>
> Cc: Stephen Warren <[email protected]>
> Cc: Thierry Reding <[email protected]>
> Cc: Dom Cobley <[email protected]>
> Cc: Linus Walleij <[email protected]>
> Cc: Arnd Bergmann <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Pawel Moll <[email protected]>
> Cc: Mark Brown <[email protected]>
> Cc: Russell King - ARM Linux <[email protected]>
> Cc: Rabeeh Khoury <[email protected]>
> Cc: Daniel Mack <[email protected]>
> Cc: Jean-Francois Moine <[email protected]>
> Cc: Lars-Peter Clausen <[email protected]>
> Cc: Guenter Roeck <[email protected]>
> Cc: Michal Bachraty <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> ---
> .../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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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
>

2013-04-10 14:48:39

by Michal Bachraty

[permalink] [raw]
Subject: Re: [PATCH v7] clk: add si5351 i2c common clock driver

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

2013-04-10 17:27:33

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [PATCH v7] clk: add si5351 i2c common clock driver

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

2013-04-10 19:34:30

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH v7] clk: add si5351 i2c common clock driver

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 <[email protected]>
> ---
> 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 <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Rob Landley <[email protected]>
> Cc: Mike Turquette <[email protected]>
> Cc: Stephen Warren <[email protected]>
> Cc: Thierry Reding <[email protected]>
> Cc: Dom Cobley <[email protected]>
> Cc: Linus Walleij <[email protected]>
> Cc: Arnd Bergmann <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Pawel Moll <[email protected]>
> Cc: Mark Brown <[email protected]>
> Cc: Russell King - ARM Linux <[email protected]>
> Cc: Rabeeh Khoury <[email protected]>
> Cc: Daniel Mack <[email protected]>
> Cc: Jean-Francois Moine <[email protected]>
> Cc: Lars-Peter Clausen <[email protected]>
> Cc: Guenter Roeck <[email protected]>

Acked-by: Guenter Roeck <[email protected]>

> Cc: Michal Bachraty <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> ---
> .../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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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
>
>

2013-04-11 07:45:09

by Michal Bachraty

[permalink] [raw]
Subject: Re: [PATCH v7] clk: add si5351 i2c common clock driver

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

2013-04-11 08:22:52

by Sebastian Hesselbarth

[permalink] [raw]
Subject: Re: [PATCH v7] clk: add si5351 i2c common clock driver

On Thu, Apr 11, 2013 at 9:44 AM, Michal Bachraty
<[email protected]> 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

2013-04-11 19:43:10

by Sebastian Hesselbarth

[permalink] [raw]
Subject: [PATCH v8] clk: add si5351 i2c common clock driver

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 <[email protected]>
Tested-by: Daniel Mack <[email protected]>
Acked-by: Guenter Roeck <[email protected]>
---
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 <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Rob Landley <[email protected]>
Cc: Mike Turquette <[email protected]>
Cc: Stephen Warren <[email protected]>
Cc: Thierry Reding <[email protected]>
Cc: Dom Cobley <[email protected]>
Cc: Linus Walleij <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Russell King - ARM Linux <[email protected]>
Cc: Rabeeh Khoury <[email protected]>
Cc: Daniel Mack <[email protected]>
Cc: Jean-Francois Moine <[email protected]>
Cc: Lars-Peter Clausen <[email protected]>
Cc: Guenter Roeck <[email protected]>
Cc: Michal Bachraty <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
---
.../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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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 <[email protected]");
+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 <[email protected]>
+ * Rabeeh Khoury <[email protected]>
+ *
+ * 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

2013-04-12 11:43:31

by Michal Bachraty

[permalink] [raw]
Subject: Re: [PATCH v8] clk: add si5351 i2c common clock driver

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 <[email protected]>

Many thanks,

Michal

> Signed-off-by: Sebastian Hesselbarth <[email protected]>
> Tested-by: Daniel Mack <[email protected]>
> Acked-by: Guenter Roeck <[email protected]>
> ---
> 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 <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Rob Landley <[email protected]>
> Cc: Mike Turquette <[email protected]>
> Cc: Stephen Warren <[email protected]>
> Cc: Thierry Reding <[email protected]>
> Cc: Dom Cobley <[email protected]>
> Cc: Linus Walleij <[email protected]>
> Cc: Arnd Bergmann <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Pawel Moll <[email protected]>
> Cc: Mark Brown <[email protected]>
> Cc: Russell King - ARM Linux <[email protected]>
> Cc: Rabeeh Khoury <[email protected]>
> Cc: Daniel Mack <[email protected]>
> Cc: Jean-Francois Moine <[email protected]>
> Cc: Lars-Peter Clausen <[email protected]>
> Cc: Guenter Roeck <[email protected]>
> Cc: Michal Bachraty <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> ---
> .../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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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

2013-04-12 18:19:43

by Mike Turquette

[permalink] [raw]
Subject: Re: [PATCH v8] clk: add si5351 i2c common clock driver

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 <[email protected]>
> Tested-by: Daniel Mack <[email protected]>
> Acked-by: Guenter Roeck <[email protected]>

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 <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Rob Landley <[email protected]>
> Cc: Mike Turquette <[email protected]>
> Cc: Stephen Warren <[email protected]>
> Cc: Thierry Reding <[email protected]>
> Cc: Dom Cobley <[email protected]>
> Cc: Linus Walleij <[email protected]>
> Cc: Arnd Bergmann <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Pawel Moll <[email protected]>
> Cc: Mark Brown <[email protected]>
> Cc: Russell King - ARM Linux <[email protected]>
> Cc: Rabeeh Khoury <[email protected]>
> Cc: Daniel Mack <[email protected]>
> Cc: Jean-Francois Moine <[email protected]>
> Cc: Lars-Peter Clausen <[email protected]>
> Cc: Guenter Roeck <[email protected]>
> Cc: Michal Bachraty <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> ---
> .../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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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 <[email protected]");
> +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 <[email protected]>
> + * Rabeeh Khoury <[email protected]>
> + *
> + * 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