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