* [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 @ 2016-08-26 21:45 York Sun [not found] ` <1472247978-29312-1-git-send-email-york.sun-3arQi8VN3Tc@public.gmane.org> 2019-05-31 14:06 ` Radu Nicolae Pirea 0 siblings, 2 replies; 6+ messages in thread From: York Sun @ 2016-08-26 21:45 UTC (permalink / raw) To: linux-clk-u79uwXL29TY76Z2rM5mHXA Cc: York Sun, Mike Turquette, Sebastian Hesselbarth, Guenter Roeck, Andrey Filippov, Paul Bolle, Stephen Boyd, Rob Herring, Mark Rutland, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA From: York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org> SI5338 is a programmable clock generator. It has 4 sets of inputs, PLL, multisynth and dividers to make 4 outputs. This driver splits them into multiple clocks to comply with common clock framework. See Documentation/devicetree/bindings/clock/silabs,si5338.txt for details. Signed-off-by: York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org> CC: Mike Turquette <mturquette-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: v9: Simply logic in clkout_status_show() by using multiple contiue instead of one conplicated conditional, as suggested by Stephen Boyd v8: Rebase to clk-next (v4.8.0-rc1) v7: Rebase to clk-next (v4.3.0-rc3) Removed unneeded header file inclusion Add static to local array Remove unneeded casting Update document according to changes Add more checks to catch divided by zero Use devm_clk_get instead of of_clk_get Move devm_clk_get to probe function, only called when needed Remove clk pointers from platform data structure v6: Fix multiline comments style Add msleep in loop of polling registers Deal with function return error consistently Remove unnecessary memset calls Fix calling strncat Use IS_ERR_OR_NULL when applicable Add a local function devm_of_clk_get() Remove clk_put in remove function Use clkout name in device tree and platform data if set Add warning if name is too long for clkdev s/if (a == 0)/if (!a) s/if (a != 0)/if (a) Remove "if (a) a = 1" and alike Add dealing with -EPROBE_DEFER for of_clk_get() Prepare the clocks before calling post init as required by HW Update module license to GPL v2 be consistent with header v5: Rebase to v4.2 Move clk-si5338.h file into clk-si5338.c Drop unused macros Add macro HWDATA to replace container_of Collapse functions for using bsearch Use return inside switch {} Separate hardware setting from recalc Rename variable ms and p123 Remove casting for returns and write_reg Change device_attr to S_IRUGO Drop sysfs, use debugfs for output status Drop exporting __clk_is_prepared Add "enabled" in device tree binding if the clocks need to be enabled when driver is loaded Add enable/disable ops for clock output 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 | 192 + drivers/clk/Kconfig | 12 + drivers/clk/Makefile | 1 + drivers/clk/clk-si5338.c | 3809 ++++++++++++++++++++ include/dt-bindings/clock/clk-si5338.h | 68 + include/linux/platform_data/si5338.h | 48 + 6 files changed, 4130 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5338.txt create mode 100644 drivers/clk/clk-si5338.c 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..cc7ae8e --- /dev/null +++ b/Documentation/devicetree/bindings/clock/silabs,si5338.txt @@ -0,0 +1,192 @@ +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. +- clock-names: The name of the clocks in the same order. +- #address-cells: shall be set to 1. +- #size-cells: shall be set to 0. +- silabs,pll-master: If PLL is used, 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. This node is not + required if PLL is not used, or if silabs,pll-vco is set. + +Optional properties if not set by platform driver: +- silab,name-prefix: name prefix for si5338 + If multiple si5338 chips exist, use name-prefix to form unique names. + If omitted, i2c bus name will be used as the prefix. +- 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-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: +- name: name for the child node + It has to be unique. The name prefix is ignored. + If using platform data and the name is not specificed, + clkout0/1/2/3 will be used with name prefix. +- 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 +- enabled: the output is enabled by default + The existence of this node enables the output when the driver is loaded + otherwise the clock is only enabled when used + +==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>, <0>, <0>, <0>, <&clkin56>; + clock-names = "xtal", "in12", "in3", "in4", "in56"; + + /* 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 source 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 bf7d540..2a185fa 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -57,6 +57,18 @@ config COMMON_CLK_SCPI This driver uses SCPI Message Protocol to interact with the firmware providing all the clock controls. +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_SI5351 tristate "Clock driver for SiLabs 5351A/B/C" depends on I2C diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index e775a83..e2e129c 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -39,6 +39,7 @@ 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_SCPI) += clk-scpi.o +obj-$(CONFIG_COMMON_CLK_SI5338) += clk-si5338.o obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o diff --git a/drivers/clk/clk-si5338.c b/drivers/clk/clk-si5338.c new file mode 100644 index 0000000..302396a --- /dev/null +++ b/drivers/clk/clk-si5338.c @@ -0,0 +1,3809 @@ +/* + * 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/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/platform_data/si5338.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/string.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 MSINT_MIN 4 /* need to exclude 5, 7 in the code */ +#define MSINT_MAX 567 + +#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_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_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_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_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_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 */ + +#define MS_POWER_DOWN 1 +#define MS_POWER_UP 0 +#define OUT_DISABLE 1 +#define OUT_ENABLE 0 +#define DRV_POWERDOWN 1 +#define DRV_POWERUP 0 + +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; +}; + +#define MAX_NAME_PREFIX 30 /* max 30 characters for the name_prefix */ +#define MAX_NAME_LENGTH 40 /* max 40 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 p[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; +}; +#define HWDATA(x) \ + ((struct si5338_hw_data *)container_of(x, struct si5338_hw_data, hw)) + +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. 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, u8 *data) +{ + u32 val; + int ret; + + ret = regmap_read(drvdata->regmap, reg, &val); + *data = (u8)val; /* si5338 has u8 value */ + + return ret; +} + +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; + + reg = awe >> 8; + mask = awe & 0xff; + if (mask) { + nshift = 0; + while (!((1 << nshift) & mask)) + 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]; i++) { + reg = awe[i] >> 8; + mask = awe[i] & 0xff; + if (mask) { + nshift = 0; + nbits = 1; + while (!((1 << nshift) & mask)) + nshift++; + while ((1 << (nshift + nbits)) & mask) + 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; +} + +/* + * The function forms a 64-bit value from multiple registers. + * The lastest value used by si5338 is 48-bit. + */ +static int read_multireg64(struct si5338_driver_data *drvdata, + const u32 *awe, u64 *data) +{ + int i, rc, nshift, nbits, full_shift = 0; + u8 val, mask; + u16 reg; + + *data = 0; + + for (i = 0; awe[i]; i++) { + reg = awe[i] >> 8; + mask = awe[i] & 0xff; + if (mask) { + nshift = 0; + nbits = 1; + while (!((1 << nshift) & mask)) + nshift++; + while ((1 << (nshift + nbits)) & mask) + nbits++; + rc = si5338_reg_read(drvdata, reg, &val); + if (rc < 0) + return rc; + + *data |= (((s64)val & mask) >> nshift) << full_shift; + full_shift += nbits; + } + } + + return 0; +} + +static int read_field(struct si5338_driver_data *drvdata, u32 awe) +{ + int rc, nshift; + u8 val, mask; + u16 reg; + + reg = awe >> 8; + mask = awe & 0xff; + + if (mask) { + nshift = 0; + while (!((1 << nshift) & mask)) + nshift++; + rc = si5338_reg_read(drvdata, reg, &val); + if (rc < 0) + return rc; + + return (val & mask) >> nshift; + } + + return 0; +} + +static int si5338_find_mask(const void *key, const void *elt) +{ + const u32 *reg = key; + const u32 *register_mask = elt; + + if (*reg > *register_mask >> 8) + return 1; + if (*reg < *register_mask >> 8) + return -1; + + return 0; +} + +static bool si5338_regmap_is_writeable(struct device *dev, unsigned int reg) +{ + return bsearch(®, register_masks, ARRAY_SIZE(register_masks), + sizeof(u32), si5338_find_mask) != NULL; +} + +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 const 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 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; +} + +/* + * Set div for the two dividers + * 0 - p1div + * 1 - p2div + * The dividers have value of 1, 2, 4, 8, 16, 32 + */ +static int set_in_pdiv(struct si5338_driver_data *drvdata, int div, int chn) +{ + u8 val; + u32 awe = chn ? AWE_P2DIV : AWE_P1DIV; + + for (val = 0; val < 6; val++) { + if ((1 << val) == div) + return write_field(drvdata, val, awe); + } + 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_hw_get_rate(hw); + 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 = HWDATA(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) +{ + struct si5338_hw_data *hwdata = &drvdata->refclk; + + hwdata->num = SI5338_FB_SRC_NOCLK; + switch (index) { + case SI5338_REF_SRC_XTAL: + /* in mux to XO */ + hwdata->num = 2; + return set_in_mux(drvdata, 2); + case SI5338_REF_SRC_CLKIN12: + /* in mux to IN12 */ + hwdata->num = 0; + return set_in_mux(drvdata, 0); + case SI5338_REF_SRC_CLKIN3: + hwdata->num = 1; + return set_in_mux(drvdata, 1); + } + dev_err(&drvdata->client->dev, + "Invalid parent (%d) for refclk\n", index); + + return -EINVAL; +} + +/* + * 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 = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + switch (index) { + case 0: + return si5338_refclk_reparent(drvdata, SI5338_REF_SRC_CLKIN12); + case 1: + return si5338_refclk_reparent(drvdata, SI5338_REF_SRC_CLKIN3); + case 2: + return si5338_refclk_reparent(drvdata, SI5338_REF_SRC_XTAL); + } + dev_err(&drvdata->client->dev, + "Invalid parent index for refclk: %d\n", index); + + return -EINVAL; +} + +static u8 si5338_refclk_get_parent(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + /* get input mux */ + return read_field(drvdata, AWE_IN_MUX); +} + +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 int si5338_divrefclk_prepare(struct clk_hw *hw) +{ + struct si5338_driver_data *drvdata = + container_of(hw, struct si5338_driver_data, divrefclk); + int idiv; + unsigned long parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw)); + + /* + * Calculate the lowest ratio to divide the input frequency to 40MHz + * or less + */ + for (idiv = 0; idiv < 5; idiv++) { + if ((parent_rate >> idiv) <= INFREQDIV) + break; + } + + return set_in_pdiv(drvdata, 1 << idiv, 0); +} + +static unsigned long si5338_divrefclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + int idiv; + + /* + * Calculate the lowest ratio to divide the input frequency to 40MHz + * or less + */ + for (idiv = 0; idiv < 5; idiv++) { + if ((parent_rate >> idiv) <= INFREQDIV) + break; + } + + return parent_rate >> idiv; +} + +static const struct clk_ops si5338_divrefclk_ops = { + .recalc_rate = si5338_divrefclk_recalc_rate, + .prepare = si5338_divrefclk_prepare, +}; + +/* + * 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; + + hwdata->num = SI5338_FB_SRC_NOCLK; + switch (index) { + case SI5338_FB_SRC_CLKIN4: + /* in mux to IN4 */ + hwdata->num = 0; + return set_fb_mux(drvdata, 1); + case SI5338_FB_SRC_CLKIN56: + /* in mux to IN56 */ + hwdata->num = 1; + return set_fb_mux(drvdata, 0); + case SI5338_FB_SRC_NOCLK: + hwdata->num = 2; + return set_fb_mux(drvdata, 2); + } + dev_err(&drvdata->client->dev, + "Invalid parent (%d) for fbclk\n", index); + + return -EINVAL; +} + +/* + * 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 = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + switch (index) { + case 0: + return si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_CLKIN4); + case 1: + return si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_CLKIN56); + case 2: + return si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_NOCLK); + } + dev_err(&drvdata->client->dev, + "Invalid parent index for fbclk\n"); + + return -EINVAL; +} + +static u8 si5338_fbclk_get_parent(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + int rc; + + /* Return value 0: IN5/IN6 + * 1: IN4 + * 2: noclk + */ + rc = read_field(drvdata, AWE_FB_MUX); + 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 int si5338_divfbclk_prepare(struct clk_hw *hw) +{ + struct si5338_driver_data *drvdata = + container_of(hw, struct si5338_driver_data, divfbclk); + int idiv; + unsigned long parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw)); + + for (idiv = 0; idiv < 5; idiv++) { + if ((parent_rate >> idiv) <= INFREQDIV) + break; + } + + return set_in_pdiv(drvdata, 1 << idiv, 1); +} + +static unsigned long si5338_divfbclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + int idiv; + + for (idiv = 0; idiv < 5; idiv++) { + if ((parent_rate >> idiv) <= INFREQDIV) + break; + } + + return parent_rate >> idiv; +} + +static const struct clk_ops si5338_divfbclk_ops = { + .recalc_rate = si5338_divfbclk_recalc_rate, + .prepare = si5338_divfbclk_prepare, +}; + +/* + * PLL and MultiSynth + */ +static int remove_common_factor(u64 *num_denom) +{ + u64 a, b, r; + + if (!num_denom[1]) + return -1; /* zero denominator */ + + if (!num_denom[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) { + 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 + } + } +}; + +static int _verify_ms_channel(struct device *dev, int chn) +{ + if (chn < 0 || chn > 4) { + dev_err(dev, + "Invalid channel %d. Only 0,1,2,3 and 4 (for MSN) are supported\n", + chn); + return -EINVAL; + } + + return 0; +} + +/* + * Read parameters of + * 0 - MS0 + * 1 - MS1 + * 2 - MS2 + * 3 - MS3 + * 4 - MSN (PLL) + */ +static int get_ms_p(struct si5338_driver_data *drvdata, u32 *p, int chn) +{ + int i, rc; + u64 data; + + rc = _verify_ms_channel(&drvdata->client->dev, chn); + if (rc < 0) + return rc; + + for (i = 0; i < 3; i++) { + rc = read_multireg64(drvdata, awe_msx[chn][i], &data); + if (rc < 0) + return rc; + + p[i] = (u32)data; /* only use up to 30 bit here */ + } + + 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 p_to_ms(u64 *ms, u32 *p) +{ + if (!p[0] && !p[1] && !p[2]) { + /* uninitialized parameters in device */ + ms[0] = 0; + ms[1] = 0; + ms[2] = 1; + } else { + /* c = p3 */ + ms[2] = p[2]; + /* b = (c * (p1 & 0x7f) + p2) >> 7 */ + ms[1] = (ms[2] * (p[0] & 0x7f) + p[1]) >> 7; + /* a = (p1 >> 7) + 4 */ + ms[0] = (p[0] >> 7) + 4; + } + pr_debug("ms[]=%llu + %llu/%llu, p=%u %u %u\n", + ms[0], ms[1], ms[2], p[0], p[1], p[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_p(struct si5338_driver_data *drvdata, + u32 *p, int chn) +{ + int i, rc, hs = 0; + + rc = _verify_ms_channel(&drvdata->client->dev, chn); + if (rc < 0) + return rc; + + /* high speed bit programming */ + if (p[0] < 512) { /* div less than 8 */ + if (p[0] < 128) + p[0] = 0; + else + p[0] = 256; + p[1] = 0; + p[2] = 1; + hs = 1; + dev_dbg(&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)p[i], + awe_msx[chn][i]); + if (rc < 0) + return rc; + } + + return 0; +} + +/* + * Calculate parameters + * ms = ms[0] + ms[1] / ms[2] + * + * SI5338 RM stats the fomula of parameters as + * p[0] = floor(((ms[0] * ms[2] + ms[1]) * 128) / ms[2] - 512) + * p[1] = mod((ms[1] * 128), ms[2]) + * p[2] = ms[2] + */ +static int ms_to_p(u64 *ms, u32 *p) +{ + 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)) { + ms_denom >>= 1; + ms_num >>= 1; + } + if (!ms_num || !ms_denom) { + ms_denom = 1; + ms_num = 0; + } + d = (ms_int * ms_denom + ms_num) << 7; + p[0] = (u32)(div64_u64(d, ms_denom) - 512); + d = div64_u64((ms_num << 7), ms_denom); + p[1] = (u32)((ms_num << 7) - d * ms_denom); + p[2] = ms_denom; + pr_debug("ms[]=%llu + %llu/%llu Hz, ms_int=%llu, ms_num=%llu, ms_denom=%llu p=%u %u %u\n", + ms[0], ms[1], ms[2], ms_int, ms_num, ms_denom, + p[0], p[1], p[2]); + + return 0; +} + +/* + * Calculate MultiSynth divider (MS0..MS3) for specified output frequency + */ +static void cal_ms_p(unsigned long numerator, + unsigned long denominator, + u32 *p) +{ + 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_warn("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_warn("MSN ratio %llu is invalid\n", ms[0]); + ms[0] += 1; + } else if (ms[0] > MSINT_MAX) { + pr_warn("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_p(ms, p); +} + +/* + * Si5338 pll section + */ +static int si5338_pll_prepare(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(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; + u8 vco_gain_rsel_bwsel; + + pll_in_freq = clk_hw_get_rate(clk_hw_get_parent(hw)); + if (!pll_in_freq) { + dev_err(&drvdata->client->dev, "Invalid input clock for pll\n"); + return -EINVAL; + } + if (!clk_get_rate(hw->clk)) { + dev_err(&drvdata->client->dev, "Invalid clock rate for pll\n"); + return -EINVAL; + } + + fvco_mhz = div64_u64(clk_hw_get_rate(hw), 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_warn(&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_warn(&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, pll_kphi, AWE_PLL_KPHI); + if (rc < 0) + return rc; + + vco_gain_rsel_bwsel = (vco_gain & 7) << 4; + vco_gain_rsel_bwsel |= (rsel & 3) << 2; + vco_gain_rsel_bwsel |= bwsel & 3; + rc = write_field(drvdata, vco_gain_rsel_bwsel, AWE_VCO_GAIN_RSEL_BWSEL); + if (rc < 0) + return rc; + + rc = write_field(drvdata, mscal, AWE_MSCAL); + if (rc < 0) + return rc; + + rc = write_field(drvdata, 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 = HWDATA(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); + if (pfd_in_ref < 0) { + dev_err(&drvdata->client->dev, + "Error getting pfd_in_ref mux\n"); + /* In case reading register fails, set to 0 */ + pfd_in_ref = 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 = HWDATA(hw); + + 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 = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + int rc; + u64 rate[3], ms[3], ms_scaled; + + if (!hwdata->params.valid) { + rc = get_ms_p(drvdata, hwdata->params.p, 4); + if (rc < 0) { + dev_err(&drvdata->client->dev, + "Error reading ms register\n"); + return 0; + } + hwdata->params.valid = true; + } + + p_to_ms(ms, hwdata->params.p); + if (unlikely(!ms[2])) { + /* + * This should not happen. Instead of crashing the system, + * set divisor to 1 and let the calculation continue. + */ + dev_warn(&drvdata->client->dev, + "Error %s calculating pll\n", __func__); + ms[2] = 1; + } + ms_scaled = ms[0] * ms[2] + ms[1]; + if (!ms_scaled) /* 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 rate[0]; +} + +static long si5338_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct si5338_hw_data *hwdata = HWDATA(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_p(rate, *parent_rate, hwdata->params.p); + hwdata->params.valid = true; + + p_to_ms(ms, hwdata->params.p); + 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 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 = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + if (unlikely(rate < FVCOMIN)) + rate = FVCOMIN; + else if (unlikely(rate > FVCOMAX)) + rate = FVCOMAX; + cal_ms_p(rate, parent_rate, hwdata->params.p); + hwdata->params.valid = true; + + return set_ms_p(drvdata, hwdata->params.p, 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; + + return write_field(drvdata, down, awe_ms_powerdown[chn]); +} + +static int si5338_msynth_prepare(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + return set_ms_powerdown(drvdata, MS_POWER_UP, hwdata->num); +} + +static void si5338_msynth_unprepare(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + set_ms_powerdown(drvdata, MS_POWER_DOWN, hwdata->num); +} + +static unsigned long si5338_msynth_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + int rc; + u64 rate[3], ms[3], ms_scaled; + + if (!hwdata->params.valid) { + rc = get_ms_p(drvdata, hwdata->params.p, hwdata->num); + if (rc < 0) + return 0; + hwdata->params.valid = true; + } + + p_to_ms(ms, hwdata->params.p); + if (unlikely(!ms[2])) { + /* This should not happen */ + dev_warn(&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) /* 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 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 = HWDATA(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_hw_get_rate( + clk_hw_get_parent(clk_hw_get_parent(hw))); + if (!pll_in_freq) { + dev_err(&drvdata->client->dev, + "Invalid input clock for MS%d\n", + hwdata->num); + return -EINVAL; + } + + 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 || 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) { + dev_warn(&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; + if (!rate) { + dev_err(&drvdata->client->dev, + "Invalid rate for MS%d\n", hwdata->num); + return -EINVAL; + } + 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_p(*parent_rate, rate, hwdata->params.p); + 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 = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + if (!rate) + rate = div64_u64(parent_rate + MSINT_MAX - 1, MSINT_MAX); + + cal_ms_p(parent_rate, rate, hwdata->params.p); + hwdata->params.valid = true; + + return set_ms_p(drvdata, hwdata->params.p, 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, +}; + +/* + * Si5338 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; + + return write_field(drvdata, dis, 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, typ, 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, 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, typ, 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, vdd, 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 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, typ, awe_drv_invert[chn]); +} + +static const u32 awe_drv_powerdown[] = { + AWE_DRV0_PDN, + AWE_DRV1_PDN, + AWE_DRV2_PDN, + AWE_DRV3_PDN +}; + +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; + + return write_field(drvdata, 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}, + + {}, +}; + +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)) + 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)) ? MS_POWER_UP : + MS_POWER_DOWN, + chn); + if (rc < 0) + return rc; + } + + return 0; +} + +static const u32 awe_fcal[] = { + AWE_FCAL_07_00, + AWE_FCAL_15_08, + AWE_FCAL_17_16, + 0 +}; + +static const 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) +{ + int rc; + + dev_dbg(&drvdata->client->dev, "Resetting MS dividers"); + /* SET MS RESET = 1 */ + rc = write_field(drvdata, 1, AWE_MS_RESET); + if (rc) + return rc; + + /* Wait for 20ms */ + msleep(20); + + /* SET MS RESET = 0 */ + rc = write_field(drvdata, 0, AWE_MS_RESET); + if (rc) + 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) + return rc; + + rc = write_field(drvdata, 0x1, AWE_MISC_106); + if (rc) + return rc; + + rc = write_field(drvdata, 0x1, AWE_MISC_116); + if (rc) + return rc; + + rc = write_field(drvdata, 0x1, AWE_MISC_42); + if (rc) + return rc; + + rc = write_field(drvdata, 0x0, AWE_MISC_06A); + if (rc) + return rc; + + rc = write_field(drvdata, 0x0, AWE_MISC_06B); + if (rc) + return rc; + + rc = write_field(drvdata, 0x0, AWE_MISC_28); + if (rc) + 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) + return rc; + + /* setup miscelalneous registers */ + rc = set_misc_registers(drvdata); + if (rc) + return rc; + + /* disable all outputs */ + rc = write_field(drvdata, 1, AWE_OUT_ALL_DIS); + if (rc) + return rc; + + /* pause LOL */ + rc = write_field(drvdata, 1, AWE_DIS_LOS); + if (rc) + return rc; + + /* clears outputs pll input/fb muxes to be set later */ + for (chn = 0; chn < 4; chn++) { + rc = set_ms_powerdown(drvdata, MS_POWER_DOWN, chn); + if (rc) + return rc; + rc = set_out_disable(drvdata, OUT_DISABLE, chn); + if (rc) + return rc; + } + /* to be explicitly enabled if needed */ + rc = set_in_pfd_ref_fb(drvdata, 5, 0); /* noclk */ + if (rc) + return rc; + + rc = set_in_pfd_ref_fb(drvdata, 5, 1); /* noclk */ + if (rc) + return rc; + + return 0; +} + +#define INIT_TIMEOUT 10 /* max 10 loops */ + +/* See SI5338 RM for programming procedure */ +static int post_init(struct si5338_driver_data *drvdata) +{ + int rc = 0, i, in_src, fb_src, ext_fb, check_los = 0; + int timeout = INIT_TIMEOUT; + u64 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)) + break; /* inputs OK */ + msleep(100); /* wait for 100ms before polling */ + } + + if (i >= timeout) { + dev_err(&drvdata->client->dev, + "Timeout waiting for input clocks, status=0x%x, mask=0x%x\n", + rc, check_los); + return -ETIMEDOUT; + } + 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); + msleep(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)) + break; /* alarms not set OK */ + msleep(100); /* wait for 100ms before polling */ + } + 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 -ETIMEDOUT; + } + 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 */ + rc = read_multireg64(drvdata, awe_fcal, &fcal); + if (rc) + return rc; + + rc = write_multireg64(drvdata, fcal, awe_fcal_ovrd); + if (rc) + 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) + return rc; + + /* SET PLL to use FCAL values, set FCAL_OVRD_EN=1 */ + rc = write_field(drvdata, 1, AWE_FCAL_OVRD_EN); + if (rc) + return rc; + + /* only needed if using down-spread. Won't hurt to do anyway */ + rc = reset_ms(drvdata); + if (rc) + return rc; + + /* Enable all (enabled individually) outputs */ + rc = write_field(drvdata, 0, AWE_OUT_ALL_DIS); + if (rc) + return rc; + + /* Clearing */ + write_field(drvdata, 0, AWE_SOFT_RESET); + + /* Power up MS if used, otherwise power down */ + rc = power_up_down_needed_ms(drvdata); + if (rc) + return rc; + + return 0; +} + +static int si5338_clkout_prepare(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + int rc; + + rc = set_drv_powerdown(drvdata, DRV_POWERUP, hwdata->num); + if (rc) { + dev_err(&drvdata->client->dev, + "Error power up clkout%d\n", hwdata->num); + } + dev_dbg(&drvdata->client->dev, "Clkout%d prepared\n", hwdata->num); + + return rc; +} + +static int si5338_clkout_enable(struct clk_hw *hw) +{ + + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + int rc; + + rc = set_out_disable(drvdata, OUT_ENABLE, hwdata->num); + if (rc) { + dev_err(&drvdata->client->dev, + "Error enabling clkout%d\n", hwdata->num); + } + dev_dbg(&drvdata->client->dev, "Clkout%d enabled\n", hwdata->num); + + return rc; +} + +static void si5338_clkout_disable(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + set_out_disable(drvdata, OUT_DISABLE, hwdata->num); + dev_dbg(&drvdata->client->dev, "Clkout%d disable\n", hwdata->num); +} + +static void si5338_clkout_unprepare(struct clk_hw *hw) +{ + struct si5338_hw_data *hwdata = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + set_drv_powerdown(drvdata, DRV_POWERDOWN, hwdata->num); + dev_dbg(&drvdata->client->dev, "Clkout%d unprepared\n", hwdata->num); +} + +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 = HWDATA(hw); + struct si5338_driver_data *drvdata = hwdata->drvdata; + + return get_out_mux(drvdata, hwdata->num); +} + +static int si5338_clkout_set_parent(struct clk_hw *hw, u8 index) +{ + struct si5338_hw_data *hwdata = HWDATA(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 = HWDATA(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_warn(&drvdata->client->dev, + "Error recalculating rate for clk%d\n", hwdata->num); + } else { + rate /= rc; + } + dev_dbg(&drvdata->client->dev, "Recalculated 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 = HWDATA(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_warn(&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 = HWDATA(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, + .enable = si5338_clkout_enable, + .disable = si5338_clkout_disable, + .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, +}; + +#ifdef CONFIG_DEBUG_FS + +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 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 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 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 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 int get_drv_trim(struct si5338_driver_data *drvdata, int chn) +{ + int rc; + u64 data; + + rc = _verify_output_channel(chn); + if (rc < 0) + return rc; + + rc = read_multireg64(drvdata, awe_drv_trim[chn], &data); + if (rc < 0) + return rc; + + return data; /* 5-bit data */ +} + +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 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]); +} + +/* + * Create debugfs files for status for each si5338 clkout + */ + +static int clkout_status_show(struct seq_file *s, void *data) +{ + struct si5338_hw_data *clkout = s->private; + struct si5338_driver_data *drvdata = clkout->drvdata; + struct si5338_drv_t const *config; + int i, j, 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 + }; + + i = clkout->num; + seq_printf(s, "%d: ", i); + if (get_out_disable(drvdata, i)) { + seq_puts(s, "disabled"); + switch (get_drv_disabled_state(drvdata, i)) { + case SI5338_OUT_DIS_HIZ: + seq_puts(s, " (high-Z)\n"); + break; + case SI5338_OUT_DIS_LOW: + seq_puts(s, " (low)\n"); + break; + case SI5338_OUT_DIS_HI: + seq_puts(s, " (high)\n"); + break; + case SI5338_OUT_DIS_ALWAYS_ON: + seq_puts(s, " (always on)\n"); + break; + } + return 0; + } + + seq_puts(s, "enabled "); + 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++) { + config = &drv_configs[j]; + if (config->fmt != drv_type) + continue; + if (config->vdd != drv_vdd) + continue; + if (config->trim != drv_trim) + continue; + if (((config->invert >> 2) | drv_invert) != + ((config->invert >> 2) | (config->invert & 3))) + continue; + + seq_puts(s, drv_configs[j].description); + match = 1; + break; + } + + if (!match) { + seq_printf(s, "Invalid output configuration: type = %d, vdd=%d, trim=%d, invert=%d\n", + drv_type, drv_vdd, drv_trim, drv_invert); + } + + seq_printf(s, ", R%d and out %d power %s", i, i, + get_drv_powerdown(drvdata, i) ? "down" : "up"); + seq_puts(s, ", Output route "); + + 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 = read_field(drvdata, AWE_FB_MUX); + 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 = read_field(drvdata, AWE_IN_MUX); + 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; + } + switch (src_group) { + case 0: + seq_printf(s, "IN%d", in_numbers[src]); + break; + case 1: + seq_puts(s, "XO"); + break; + case 2: + seq_printf(s, "MS%d", src); + break; + case 3: + seq_puts(s, "No clock"); + break; + } + + if (out_src == 5 || out_src == 6) { + seq_printf(s, " power %s", + get_ms_powerdown(drvdata, i) ? "down" : "up"); + } + + seq_puts(s, "\n"); + + return 0; +} + +static int clkout_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, clkout_status_show, inode->i_private); +} + +static const struct file_operations clkout_status_fops = { + .open = clkout_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int register_debugfs_status(struct si5338_hw_data *clkout) +{ + struct dentry *d; + + d = clk_debugfs_add_file(&clkout->hw, "output_status", S_IRUGO, + clkout, &clkout_status_fops); + if (!d) + return -ENOMEM; + + return 0; +} + +#else +static int register_debugfs_status(struct si5338_hw_data *clkout) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +/* + * 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; + + if (np == NULL) + return 0; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + /* 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); + return -EINVAL; + } + } + + /* 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); + return -EINVAL; + } + } + + /* 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); + return -EINVAL; + } + } + + /* 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); + return -EINVAL; + } + 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; + } + + of_property_read_string(child, "name", + &pdata->clkout[num].name); + + 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); + } + if (of_find_property(child, "enabled", NULL)) + pdata->clkout[num].enabled = true; + } + /* Replace platform data with device tree */ + 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; +} + +#define STRNCAT_LENGTH (MAX_NAME_LENGTH - name_prefix_length) +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_LENGTH]; + char register_name[MAX_NAME_LENGTH]; + const char *pclkin[4] = {"in12", "in3", "in4", "in56"}; + 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, name_prefix_length; + 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; + if (!pdata->name_prefix) { + strlcpy(drvdata->name_prefix, + dev_name(&client->dev), MAX_NAME_PREFIX - 2); + strncat(drvdata->name_prefix, "-", 1); + } else { + strlcpy(drvdata->name_prefix, + pdata->name_prefix, MAX_NAME_PREFIX); + } + name_prefix_length = strlen(drvdata->name_prefix); + + /* 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) + 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 (require_xtal) { + dev_err(&client->dev, + "Error in configuration: IN1/IN2 and XTAL are mutually exclusive\n"); + return -EINVAL; + } + drvdata->pclkin[0] = devm_clk_get(&client->dev, + pclkin[0]); + if (PTR_ERR(drvdata->pclkin[0]) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (IS_ERR_OR_NULL(drvdata->pclkin[0])) { + dev_err(&client->dev, + "IN1/IN2 doesn't a have source\n"); + return -EINVAL; + } + break; + case SI5338_REF_SRC_CLKIN3: + drvdata->pclkin[1] = devm_clk_get(&client->dev, + pclkin[1]); + if (PTR_ERR(drvdata->pclkin[1]) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (IS_ERR_OR_NULL(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: + drvdata->pclkin[2] = devm_clk_get(&client->dev, + pclkin[2]); + if (PTR_ERR(drvdata->pclkin[2]) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (IS_ERR_OR_NULL(drvdata->pclkin[2])) { + dev_err(&client->dev, + "IN4 doesn't have a source\n"); + return -EINVAL; + } + break; + case SI5338_FB_SRC_CLKIN56: + drvdata->pclkin[3] = devm_clk_get(&client->dev, + pclkin[3]); + if (PTR_ERR(drvdata->pclkin[3]) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (IS_ERR_OR_NULL(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) { + drvdata->pxtal = devm_clk_get(&client->dev, "xtal"); + if (PTR_ERR(drvdata->pxtal) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (IS_ERR_OR_NULL(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 0x%x: 0x%x, expected 0x%x. It is not %s\n", + REG5338_DEV_CONFIG2, n, REG5338_DEV_CONFIG2_VAL, + id->name); + return -ENODEV; + } + + dev_dbg(&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; + } + } + + /* + * To form clock names, concatentate name prefix with each name. + * The result string is up to MAX_NAME_LENGTH including termination. + */ + + /* Register xtal input clock */ + if (!IS_ERR_OR_NULL(drvdata->pxtal)) { + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_input_names[4], STRNCAT_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_OR_NULL(drvdata->pclkin[n])) + continue; + + drvdata->clkin[n].drvdata = drvdata; + drvdata->clkin[n].num = n; + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_input_names[n], STRNCAT_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; + 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], STRNCAT_LENGTH); + strncat(name_buf[1], si5338_input_names[1], STRNCAT_LENGTH); + strncat(name_buf[2], si5338_input_names[4], STRNCAT_LENGTH); + strncat(name_buf[3], si5338_input_names[5], STRNCAT_LENGTH); + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_pll_src_names[0], STRNCAT_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 */ + strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(name_buf[0], si5338_pll_src_names[0], STRNCAT_LENGTH); + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_pll_src_names[2], STRNCAT_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; + 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], STRNCAT_LENGTH); + strncat(name_buf[1], si5338_input_names[3], STRNCAT_LENGTH); + strncat(name_buf[2], si5338_input_names[5], STRNCAT_LENGTH); + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_pll_src_names[1], STRNCAT_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 */ + strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(name_buf[0], si5338_pll_src_names[1], STRNCAT_LENGTH); + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_pll_src_names[3], STRNCAT_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; + 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], STRNCAT_LENGTH); + } + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_msynth_src_names[0], STRNCAT_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) { + dev_err(&client->dev, "Cannot set pll vco rate : %d\n", + ret); + 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); + if (!drvdata->msynth) + return -ENOMEM; + + drvdata->clkout = devm_kzalloc(&client->dev, 4 * + sizeof(*drvdata->clkout), GFP_KERNEL); + if (!drvdata->clkout) + return -ENOMEM; + + drvdata->onecell.clk_num = 4; + drvdata->onecell.clks = devm_kzalloc(&client->dev, + 4 * sizeof(*drvdata->onecell.clks), GFP_KERNEL); + + if (!drvdata->onecell.clks) + return -ENOMEM; + + for (n = 0; n < 4; n++) { + drvdata->msynth[n].num = n; + drvdata->msynth[n].drvdata = drvdata; + strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(name_buf[0], si5338_msynth_src_names[0], + STRNCAT_LENGTH); + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); + strncat(register_name, si5338_msynth_names[n], STRNCAT_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 + */ + 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], + STRNCAT_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], STRNCAT_LENGTH); + /* + * Use clkout_name from device tree or platform data ignoring + * name_prefix. The clkout_name must be unique for each clock. + */ + if (pdata->clkout[n].name) { + if (strlen(pdata->clkout[n].name) >= MAX_NAME_LENGTH) { + dev_warn(&client->dev, + "clkout[%d] name %s too long\n", + n, pdata->clkout[n].name); + } + strlcpy(register_name, pdata->clkout[n].name, + MAX_NAME_LENGTH); + } else { + strlcpy(register_name, drvdata->name_prefix, + MAX_NAME_PREFIX); + strncat(register_name, si5338_clkout_names[n], + STRNCAT_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); + + if (register_debugfs_status(&drvdata->clkout[n])) { + dev_warn(&client->dev, + "Failed to register clkout status in debugfs\n"); + } + + drvdata->onecell.clks[n] = clk; + + /* set initial clkout rate */ + if (pdata->clkout[n].rate) { + 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) { + dev_err(&client->dev, + "Cannot set rate for clkout%d: %d\n", + n, ret); + return ret; + } + /* clocks need to be prepared before post init */ + ret = clk_prepare(clk); + if (ret) { + dev_err(&client->dev, + "Cannot prepare clk%d\n", n); + return ret; + } + } + } + + /* + * 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; + + for (n = 0; n < 4; n++) { + if (pdata->clkout[n].rate) { + if (pdata->clkout[n].enabled) { + ret = clk_enable(drvdata->onecell.clks[n]); + if (ret) + return ret; + } else { + clk_unprepare(drvdata->onecell.clks[n]); + } + } + } + + dev_dbg(&client->dev, "%s clocks are registered\n", id->name); + +#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_warn(&client->dev, + "Unable to add clkout%d to clkdev\n", n); + continue; + } + if (strlen(drvdata->lookup[n]->con_id) != + strlen(__clk_get_name(clk))) { + dev_warn(&client->dev, + "Warning: clkdev doesn't support name longer than %zu\n", + strlen(drvdata->lookup[n]->con_id)); + } + 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; + +#ifdef CONFIG_OF + of_clk_del_provider(client->dev.of_node); +#endif + + for (n = 0; n < 4; n++) { + if (drvdata->lookup[n]) + clkdev_drop(drvdata->lookup[n]); + } + + dev_dbg(&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 v2"); 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..5422955 --- /dev/null +++ b/include/linux/platform_data/si5338.h @@ -0,0 +1,48 @@ +/* + * 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 + * @name: clkout name. If omitted, clkout0/1/2/3 with name_prefix will be used + * @clkout_src: clkout source clock + * @drive: output drive strength + * @rate: initial clkout rate, or default if 0 + * @enabled: output enabled by default + */ +struct si5338_clkout_config { + const char *name; + u8 clkout_src; + const char *drive; + u8 disable_state; + unsigned long rate; + bool enabled; +}; + +/* + * 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 + * @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; + u8 ref_src; + u8 fb_src; + u8 pll_src; + u8 pll_master; + u32 pll_vco; + struct si5338_clkout_config clkout[4]; +}; + +#endif -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply related [flat|nested] 6+ messages in thread
[parent not found: <1472247978-29312-1-git-send-email-york.sun-3arQi8VN3Tc@public.gmane.org>]
* Re: [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 [not found] ` <1472247978-29312-1-git-send-email-york.sun-3arQi8VN3Tc@public.gmane.org> @ 2016-09-02 14:04 ` Rob Herring 2016-09-02 15:57 ` york sun 0 siblings, 1 reply; 6+ messages in thread From: Rob Herring @ 2016-09-02 14:04 UTC (permalink / raw) To: York Sun Cc: linux-clk-u79uwXL29TY76Z2rM5mHXA, York Sun, Mike Turquette, Sebastian Hesselbarth, Guenter Roeck, Andrey Filippov, Paul Bolle, Stephen Boyd, Mark Rutland, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA On Fri, Aug 26, 2016 at 02:45:49PM -0700, York Sun wrote: > From: York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org> > > SI5338 is a programmable clock generator. It has 4 sets of inputs, > PLL, multisynth and dividers to make 4 outputs. This driver splits > them into multiple clocks to comply with common clock framework. > > See Documentation/devicetree/bindings/clock/silabs,si5338.txt for > details. > > Signed-off-by: York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org> > CC: Mike Turquette <mturquette-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> 7 versions before you decide to start cc'ing DT list? [...] > diff --git a/Documentation/devicetree/bindings/clock/silabs,si5338.txt b/Documentation/devicetree/bindings/clock/silabs,si5338.txt > new file mode 100644 > index 0000000..cc7ae8e > --- /dev/null > +++ b/Documentation/devicetree/bindings/clock/silabs,si5338.txt > @@ -0,0 +1,192 @@ > +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. > +- clock-names: The name of the clocks in the same order. > +- #address-cells: shall be set to 1. > +- #size-cells: shall be set to 0. > +- silabs,pll-master: If PLL is used, 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. This node is not > + required if PLL is not used, or if silabs,pll-vco is set. > + > +Optional properties if not set by platform driver: > +- silab,name-prefix: name prefix for si5338 > + If multiple si5338 chips exist, use name-prefix to form unique names. > + If omitted, i2c bus name will be used as the prefix. Drop this please. There should not be any need for it. > +- 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-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: > +- name: name for the child node > + It has to be unique. The name prefix is ignored. > + If using platform data and the name is not specificed, > + clkout0/1/2/3 will be used with name prefix. > +- 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. May be long, but needs to be documented here. I don't follow why you are using strings for the values here. > +- 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 > +- enabled: the output is enabled by default > + The existence of this node enables the output when the driver is loaded > + otherwise the clock is only enabled when used Drop this. Either platform code should claim this and enable the clock or use the critical clocks binding. > + > +==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>, <0>, <0>, <0>, <&clkin56>; > + clock-names = "xtal", "in12", "in3", "in4", "in56"; > + > + /* 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 source 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 property means you need a unit address. > + 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>; > + }; > + > + }; > +}; -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 2016-09-02 14:04 ` Rob Herring @ 2016-09-02 15:57 ` york sun 0 siblings, 0 replies; 6+ messages in thread From: york sun @ 2016-09-02 15:57 UTC (permalink / raw) To: Rob Herring Cc: linux-clk@vger.kernel.org, York Sun, Mike Turquette, Sebastian Hesselbarth, Guenter Roeck, Andrey Filippov, Paul Bolle, Stephen Boyd, Mark Rutland, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org On 09/02/2016 07:04 AM, Rob Herring wrote: > On Fri, Aug 26, 2016 at 02:45:49PM -0700, York Sun wrote: >> From: York Sun <yorksun@freescale.com> >> >> SI5338 is a programmable clock generator. It has 4 sets of inputs, >> PLL, multisynth and dividers to make 4 outputs. This driver splits >> them into multiple clocks to comply with common clock framework. >> >> See Documentation/devicetree/bindings/clock/silabs,si5338.txt for >> details. >> >> Signed-off-by: York Sun <yorksun@freescale.com> >> CC: Mike Turquette <mturquette@baylibre.com> >> CC: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> >> CC: Guenter Roeck <linux@roeck-us.net> >> CC: Andrey Filippov <andrey@elphel.com> >> CC: Paul Bolle <pebolle@tiscali.nl> > > 7 versions before you decide to start cc'ing DT list? 7? I only started to CC DT list since version 8. Didn't know I needed to. I thought device tree binding is part of driver. > > [...] > >> diff --git a/Documentation/devicetree/bindings/clock/silabs,si5338.txt b/Documentation/devicetree/bindings/clock/silabs,si5338.txt >> new file mode 100644 >> index 0000000..cc7ae8e >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/clock/silabs,si5338.txt >> @@ -0,0 +1,192 @@ >> +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. >> +- clock-names: The name of the clocks in the same order. >> +- #address-cells: shall be set to 1. >> +- #size-cells: shall be set to 0. >> +- silabs,pll-master: If PLL is used, 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. This node is not >> + required if PLL is not used, or if silabs,pll-vco is set. >> + >> +Optional properties if not set by platform driver: >> +- silab,name-prefix: name prefix for si5338 >> + If multiple si5338 chips exist, use name-prefix to form unique names. >> + If omitted, i2c bus name will be used as the prefix. > > Drop this please. There should not be any need for it. Which part? The default name, or slab,name-prefix completely? When multiple si5338 chips exist, we need to name the clocks differently, don't we? > >> +- 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-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: >> +- name: name for the child node >> + It has to be unique. The name prefix is ignored. >> + If using platform data and the name is not specificed, >> + clkout0/1/2/3 will be used with name prefix. >> +- 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. > > May be long, but needs to be documented here. I don't follow why you are > using strings for the values here. Using string so it is readable. The acceptable values are 3V3_CMOS_A+ 3V3_CMOS_A- 3V3_CMOS_B+ 3V3_CMOS_B- 3V3_CMOS_A+B+ 3V3_CMOS_A-B+ 3V3_CMOS_A+B- 3V3_CMOS_A-B- 2V5_CMOS_A+ 2V5_CMOS_A- 2V5_CMOS_B+ 2V5_CMOS_B- 2V5_CMOS_A+B+ 2V5_CMOS_A-B+ 2V5_CMOS_A+B- 2V5_CMOS_A-B- 1V8_CMOS_A+ 1V8_CMOS_A- 1V8_CMOS_B+ 1V8_CMOS_B- 1V8_CMOS_A+B+ 1V8_CMOS_A-B+ 1V8_CMOS_A+B- 1V8_CMOS_A-B- 1V5_HSTL_A+ 1V5_HSTL_A- 1V5_HSTL_B+ 1V5_HSTL_B- 1V5_HSTL_A+B+ 1V5_HSTL_A-B+ 1V5_HSTL_A+B- 1V5_HSTL_A-B- 3V3_SSTL_A+ 3V3_SSTL_A- 3V3_SSTL_B+ 3V3_SSTL_B- 3V3_SSTL_A+B+ 3V3_SSTL_A-B+ 3V3_SSTL_A+B- 3V3_SSTL_A-B- 2V5_SSTL_A+ 2V5_SSTL_A- 2V5_SSTL_B+ 2V5_SSTL_B- 2V5_SSTL_A+B+ 2V5_SSTL_A-B+ 2V5_SSTL_A+B- 2V5_SSTL_A-B- 1V8_SSTL_A+ 1V8_SSTL_A- 1V8_SSTL_B+ 1V8_SSTL_B- 1V8_SSTL_A+B+ 1V8_SSTL_A-B+ 1V8_SSTL_A+B- 1V8_SSTL_A-B- 3V3_LVPECL 2V5_LVPECL 3V3_LVDS 2V5_LVDS 1V8_LVDS Will add here in next version. > >> +- 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 >> +- enabled: the output is enabled by default >> + The existence of this node enables the output when the driver is loaded >> + otherwise the clock is only enabled when used > > Drop this. Either platform code should claim this and enable the clock > or use the critical clocks binding. The idea is to specify if the clock(s) should be enabled upon loading the driver. I am not familiar with critical clocks binding. Can you point me to its document/code? Not seeing it in kernel tree. > >> + >> +==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>, <0>, <0>, <0>, <&clkin56>; >> + clock-names = "xtal", "in12", "in3", "in4", "in56"; >> + >> + /* 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 source 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 property means you need a unit address. The reg has the address of each output, i.e. 0, 1, 2, 3. What's wrong here? > >> + 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>; >> + }; >> + >> + }; >> +}; I see no more comment below. Are you OK with the rest of the patch? York ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 2016-08-26 21:45 [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 York Sun [not found] ` <1472247978-29312-1-git-send-email-york.sun-3arQi8VN3Tc@public.gmane.org> @ 2019-05-31 14:06 ` Radu Nicolae Pirea 2019-05-31 16:47 ` [EXT] " York Sun 2019-06-06 18:05 ` Stephen Boyd 1 sibling, 2 replies; 6+ messages in thread From: Radu Nicolae Pirea @ 2019-05-31 14:06 UTC (permalink / raw) To: York Sun, linux-clk Cc: York Sun, Mike Turquette, Sebastian Hesselbarth, Guenter Roeck, Andrey Filippov, Paul Bolle, Stephen Boyd, Rob Herring, Mark Rutland, devicetree, linux-kernel Hi, @York I want to continue the work on this driver and I want to upstream it. Are you OK with this? I saw later improvement suggestions related to the bindings and I will make the changes. @all please look at my below comment about si5338_pll_round_rate function. On Fri, 2016-08-26 at 14:45 -0700, York Sun wrote: > From: York Sun <yorksun@freescale.com> > > SI5338 is a programmable clock generator. It has 4 sets of inputs, > PLL, multisynth and dividers to make 4 outputs. This driver splits > them into multiple clocks to comply with common clock framework. > > See Documentation/devicetree/bindings/clock/silabs,si5338.txt for > details. > > Signed-off-by: York Sun <yorksun@freescale.com> > CC: Mike Turquette <mturquette@baylibre.com> > CC: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> > CC: Guenter Roeck <linux@roeck-us.net> > CC: Andrey Filippov <andrey@elphel.com> > CC: Paul Bolle <pebolle@tiscali.nl> > --- > Change log: > v9: Simply logic in clkout_status_show() by using multiple contiue > instead > of one conplicated conditional, as suggested by Stephen Boyd > > v8: Rebase to clk-next (v4.8.0-rc1) > > v7: Rebase to clk-next (v4.3.0-rc3) > Removed unneeded header file inclusion > Add static to local array > Remove unneeded casting > Update document according to changes > Add more checks to catch divided by zero > Use devm_clk_get instead of of_clk_get > Move devm_clk_get to probe function, only called when needed > Remove clk pointers from platform data structure > > v6: Fix multiline comments style > Add msleep in loop of polling registers > Deal with function return error consistently > Remove unnecessary memset calls > Fix calling strncat > Use IS_ERR_OR_NULL when applicable > Add a local function devm_of_clk_get() > Remove clk_put in remove function > Use clkout name in device tree and platform data if set > Add warning if name is too long for clkdev > s/if (a == 0)/if (!a) > s/if (a != 0)/if (a) > Remove "if (a) a = 1" and alike > Add dealing with -EPROBE_DEFER for of_clk_get() > Prepare the clocks before calling post init as required by HW > Update module license to GPL v2 be consistent with header > > v5: Rebase to v4.2 > Move clk-si5338.h file into clk-si5338.c > Drop unused macros > Add macro HWDATA to replace container_of > Collapse functions for using bsearch > Use return inside switch {} > Separate hardware setting from recalc > Rename variable ms and p123 > Remove casting for returns and write_reg > Change device_attr to S_IRUGO > Drop sysfs, use debugfs for output status > Drop exporting __clk_is_prepared > Add "enabled" in device tree binding if the clocks need to be > enabled when driver is loaded > Add enable/disable ops for clock output > > 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 | 192 + > drivers/clk/Kconfig | 12 + > drivers/clk/Makefile | 1 + > drivers/clk/clk-si5338.c | 3809 > ++++++++++++++++++++ > include/dt-bindings/clock/clk-si5338.h | 68 + > include/linux/platform_data/si5338.h | 48 + > 6 files changed, 4130 insertions(+) > create mode 100644 > Documentation/devicetree/bindings/clock/silabs,si5338.txt > create mode 100644 drivers/clk/clk-si5338.c > 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..cc7ae8e > --- /dev/null > +++ b/Documentation/devicetree/bindings/clock/silabs,si5338.txt > @@ -0,0 +1,192 @@ > +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. > +- clock-names: The name of the clocks in the same order. > +- #address-cells: shall be set to 1. > +- #size-cells: shall be set to 0. > +- silabs,pll-master: If PLL is used, 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. This node is not > + required if PLL is not used, or if silabs,pll-vco is set. > + > +Optional properties if not set by platform driver: > +- silab,name-prefix: name prefix for si5338 > + If multiple si5338 chips exist, use name-prefix to form > unique names. > + If omitted, i2c bus name will be used as the prefix. > +- silab,ref-source: source of refclk, valid value is defined as I think is better to remove the "#define" from here and suggest including the header file. > + #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-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: > +- name: name for the child node > + It has to be unique. The name prefix is ignored. > + If using platform data and the name is not specificed, > + clkout0/1/2/3 will be used with name prefix. > +- 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. Here must be specified all values for silabs,drive-config. > +- 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 > +- enabled: the output is enabled by default > + The existence of this node enables the output when the driver > is loaded > + otherwise the clock is only enabled when used > + > +==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>, <0>, <0>, <0>, <&clkin56>; > + clock-names = "xtal", "in12", "in3", "in4", "in56"; > + > + /* 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 source 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 bf7d540..2a185fa 100644 > --- a/drivers/clk/Kconfig > +++ b/drivers/clk/Kconfig > @@ -57,6 +57,18 @@ config COMMON_CLK_SCPI > This driver uses SCPI Message Protocol to interact with the > firmware providing all the clock controls. > > +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_SI5351 > tristate "Clock driver for SiLabs 5351A/B/C" > depends on I2C > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile > index e775a83..e2e129c 100644 > --- a/drivers/clk/Makefile > +++ b/drivers/clk/Makefile > @@ -39,6 +39,7 @@ 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_SCPI) += clk-scpi.o > +obj-$(CONFIG_COMMON_CLK_SI5338) += clk-si5338.o > obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o > obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o > obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o > diff --git a/drivers/clk/clk-si5338.c b/drivers/clk/clk-si5338.c > new file mode 100644 > index 0000000..302396a > --- /dev/null > +++ b/drivers/clk/clk-si5338.c > @@ -0,0 +1,3809 @@ > +/* > + * clk-si5338.c: Silicon Labs Si5338 I2C Clock Generator > + * > + * Copyright 2015 Freescale Semiconductor > + * York Sun <yorksun@freescale.com> > + * > + * Some code is taken from si5338.c by Andrey Filippov < > andrey@elphel.com> > + * Copyright 2013 Elphel, Inc. > + * > + * SI5338 has several blocks, including > + * Inputs (IN1/IN2, IN3, IN4, IN5/IN6, XTAL) > + * PLL (Synthesis stage 1) > + * MultiSynth (Synthesis state 2) > + * Outputs (OUT0/1/2/3) > + * Each block is registered as a clock device to form a tree > structure. > + * See Documentation/devicetree/bindings/clock/silabs,si5338.txt for > details. > + * > + * This driver uses regmap to cache register values to reduce > transactions > + * on I2C bus. Volatile registers are specified. > + * > + * This program is free software; you can redistribute it and/or > modify it > + * under the terms of the GNU General Public License as published > by the > + * Free Software Foundation; either version 2 of the License, or > (at your > + * option) any later version. > + */ > + > +#include <dt-bindings/clock/clk-si5338.h> > +#include <linux/bsearch.h> > +#include <linux/clk.h> > +#include <linux/clkdev.h> > +#include <linux/clk-provider.h> > +#include <linux/debugfs.h> > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/errno.h> > +#include <linux/kernel.h> > +#include <linux/i2c.h> > +#include <linux/math64.h> > +#include <linux/module.h> > +#include <linux/platform_data/si5338.h> > +#include <linux/regmap.h> > +#include <linux/slab.h> > +#include <linux/string.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 MSINT_MIN 4 /* need to exclude 5, 7 in the code > */ > +#define MSINT_MAX 567 > + > +#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_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_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_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_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_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 */ > + > +#define MS_POWER_DOWN 1 > +#define MS_POWER_UP 0 > +#define OUT_DISABLE 1 > +#define OUT_ENABLE 0 > +#define DRV_POWERDOWN 1 > +#define DRV_POWERUP 0 > + > +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; > +}; > + > +#define MAX_NAME_PREFIX 30 /* max 30 characters for the name_prefix > */ > +#define MAX_NAME_LENGTH 40 /* max 40 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 p[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; > +}; > +#define HWDATA(x) \ > + ((struct si5338_hw_data *)container_of(x, struct > si5338_hw_data, hw)) > + > +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. 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, u8 *data) > +{ > + u32 val; > + int ret; > + > + ret = regmap_read(drvdata->regmap, reg, &val); > + *data = (u8)val; /* si5338 has u8 value */ > + > + return ret; > +} > + > +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; > + > + reg = awe >> 8; > + mask = awe & 0xff; > + if (mask) { > + nshift = 0; > + while (!((1 << nshift) & mask)) > + 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]; i++) { > + reg = awe[i] >> 8; > + mask = awe[i] & 0xff; > + if (mask) { > + nshift = 0; > + nbits = 1; > + while (!((1 << nshift) & mask)) > + nshift++; > + while ((1 << (nshift + nbits)) & mask) > + 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; > +} > + > +/* > + * The function forms a 64-bit value from multiple registers. > + * The lastest value used by si5338 is 48-bit. > + */ > +static int read_multireg64(struct si5338_driver_data *drvdata, > + const u32 *awe, u64 *data) > +{ > + int i, rc, nshift, nbits, full_shift = 0; > + u8 val, mask; > + u16 reg; > + > + *data = 0; > + > + for (i = 0; awe[i]; i++) { > + reg = awe[i] >> 8; > + mask = awe[i] & 0xff; > + if (mask) { > + nshift = 0; > + nbits = 1; > + while (!((1 << nshift) & mask)) > + nshift++; > + while ((1 << (nshift + nbits)) & mask) > + nbits++; > + rc = si5338_reg_read(drvdata, reg, &val); > + if (rc < 0) > + return rc; > + > + *data |= (((s64)val & mask) >> nshift) << > full_shift; > + full_shift += nbits; > + } > + } > + > + return 0; > +} > + > +static int read_field(struct si5338_driver_data *drvdata, u32 awe) > +{ > + int rc, nshift; > + u8 val, mask; > + u16 reg; > + > + reg = awe >> 8; > + mask = awe & 0xff; > + > + if (mask) { > + nshift = 0; > + while (!((1 << nshift) & mask)) > + nshift++; > + rc = si5338_reg_read(drvdata, reg, &val); > + if (rc < 0) > + return rc; > + > + return (val & mask) >> nshift; > + } > + > + return 0; > +} > + > +static int si5338_find_mask(const void *key, const void *elt) > +{ > + const u32 *reg = key; > + const u32 *register_mask = elt; > + > + if (*reg > *register_mask >> 8) > + return 1; > + if (*reg < *register_mask >> 8) > + return -1; > + > + return 0; > +} > + > +static bool si5338_regmap_is_writeable(struct device *dev, unsigned > int reg) > +{ > + return bsearch(®, register_masks, > ARRAY_SIZE(register_masks), > + sizeof(u32), si5338_find_mask) != NULL; > +} > + > +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 const 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 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; > +} > + > +/* > + * Set div for the two dividers > + * 0 - p1div > + * 1 - p2div > + * The dividers have value of 1, 2, 4, 8, 16, 32 > + */ > +static int set_in_pdiv(struct si5338_driver_data *drvdata, int div, > int chn) > +{ > + u8 val; > + u32 awe = chn ? AWE_P2DIV : AWE_P1DIV; > + > + for (val = 0; val < 6; val++) { > + if ((1 << val) == div) > + return write_field(drvdata, val, awe); > + } > + 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_hw_get_rate(hw); > + 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 = HWDATA(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) > +{ > + struct si5338_hw_data *hwdata = &drvdata->refclk; > + > + hwdata->num = SI5338_FB_SRC_NOCLK; > + switch (index) { > + case SI5338_REF_SRC_XTAL: > + /* in mux to XO */ > + hwdata->num = 2; > + return set_in_mux(drvdata, 2); > + case SI5338_REF_SRC_CLKIN12: > + /* in mux to IN12 */ > + hwdata->num = 0; > + return set_in_mux(drvdata, 0); > + case SI5338_REF_SRC_CLKIN3: > + hwdata->num = 1; > + return set_in_mux(drvdata, 1); > + } > + dev_err(&drvdata->client->dev, > + "Invalid parent (%d) for refclk\n", index); > + > + return -EINVAL; > +} > + > +/* > + * 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 = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + switch (index) { > + case 0: > + return si5338_refclk_reparent(drvdata, > SI5338_REF_SRC_CLKIN12); > + case 1: > + return si5338_refclk_reparent(drvdata, > SI5338_REF_SRC_CLKIN3); > + case 2: > + return si5338_refclk_reparent(drvdata, > SI5338_REF_SRC_XTAL); > + } > + dev_err(&drvdata->client->dev, > + "Invalid parent index for refclk: %d\n", index); > + > + return -EINVAL; > +} > + > +static u8 si5338_refclk_get_parent(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + /* get input mux */ > + return read_field(drvdata, AWE_IN_MUX); > +} > + > +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 int si5338_divrefclk_prepare(struct clk_hw *hw) > +{ > + struct si5338_driver_data *drvdata = > + container_of(hw, struct si5338_driver_data, divrefclk); > + int idiv; > + unsigned long parent_rate = > clk_hw_get_rate(clk_hw_get_parent(hw)); > + > + /* > + * Calculate the lowest ratio to divide the input frequency to > 40MHz > + * or less > + */ > + for (idiv = 0; idiv < 5; idiv++) { > + if ((parent_rate >> idiv) <= INFREQDIV) > + break; > + } > + > + return set_in_pdiv(drvdata, 1 << idiv, 0); > +} > + > +static unsigned long si5338_divrefclk_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + int idiv; > + > + /* > + * Calculate the lowest ratio to divide the input frequency to > 40MHz > + * or less > + */ > + for (idiv = 0; idiv < 5; idiv++) { > + if ((parent_rate >> idiv) <= INFREQDIV) > + break; > + } > + > + return parent_rate >> idiv; > +} > + > +static const struct clk_ops si5338_divrefclk_ops = { > + .recalc_rate = si5338_divrefclk_recalc_rate, > + .prepare = si5338_divrefclk_prepare, > +}; > + > +/* > + * 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; > + > + hwdata->num = SI5338_FB_SRC_NOCLK; > + switch (index) { > + case SI5338_FB_SRC_CLKIN4: > + /* in mux to IN4 */ > + hwdata->num = 0; > + return set_fb_mux(drvdata, 1); > + case SI5338_FB_SRC_CLKIN56: > + /* in mux to IN56 */ > + hwdata->num = 1; > + return set_fb_mux(drvdata, 0); > + case SI5338_FB_SRC_NOCLK: > + hwdata->num = 2; > + return set_fb_mux(drvdata, 2); > + } > + dev_err(&drvdata->client->dev, > + "Invalid parent (%d) for fbclk\n", index); > + > + return -EINVAL; > +} > + > +/* > + * 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 = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + switch (index) { > + case 0: > + return si5338_fbclk_reparent(drvdata, > SI5338_FB_SRC_CLKIN4); > + case 1: > + return si5338_fbclk_reparent(drvdata, > SI5338_FB_SRC_CLKIN56); > + case 2: > + return si5338_fbclk_reparent(drvdata, > SI5338_FB_SRC_NOCLK); > + } > + dev_err(&drvdata->client->dev, > + "Invalid parent index for fbclk\n"); > + > + return -EINVAL; > +} > + > +static u8 si5338_fbclk_get_parent(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + int rc; > + > + /* Return value 0: IN5/IN6 > + * 1: IN4 > + * 2: noclk > + */ > + rc = read_field(drvdata, AWE_FB_MUX); > + 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 int si5338_divfbclk_prepare(struct clk_hw *hw) > +{ > + struct si5338_driver_data *drvdata = > + container_of(hw, struct si5338_driver_data, divfbclk); > + int idiv; > + unsigned long parent_rate = > clk_hw_get_rate(clk_hw_get_parent(hw)); > + > + for (idiv = 0; idiv < 5; idiv++) { > + if ((parent_rate >> idiv) <= INFREQDIV) > + break; > + } > + > + return set_in_pdiv(drvdata, 1 << idiv, 1); > +} > + > +static unsigned long si5338_divfbclk_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + int idiv; > + > + for (idiv = 0; idiv < 5; idiv++) { > + if ((parent_rate >> idiv) <= INFREQDIV) > + break; > + } > + > + return parent_rate >> idiv; > +} > + > +static const struct clk_ops si5338_divfbclk_ops = { > + .recalc_rate = si5338_divfbclk_recalc_rate, > + .prepare = si5338_divfbclk_prepare, > +}; > + > +/* > + * PLL and MultiSynth > + */ > +static int remove_common_factor(u64 *num_denom) > +{ > + u64 a, b, r; > + > + if (!num_denom[1]) > + return -1; /* zero denominator */ > + > + if (!num_denom[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) { > + 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 > + } > + } > +}; > + > +static int _verify_ms_channel(struct device *dev, int chn) > +{ > + if (chn < 0 || chn > 4) { > + dev_err(dev, > + "Invalid channel %d. Only 0,1,2,3 and 4 (for > MSN) are supported\n", > + chn); > + return -EINVAL; > + } > + > + return 0; > +} > + > +/* > + * Read parameters of > + * 0 - MS0 > + * 1 - MS1 > + * 2 - MS2 > + * 3 - MS3 > + * 4 - MSN (PLL) > + */ > +static int get_ms_p(struct si5338_driver_data *drvdata, u32 *p, int > chn) > +{ > + int i, rc; > + u64 data; > + > + rc = _verify_ms_channel(&drvdata->client->dev, chn); > + if (rc < 0) > + return rc; > + > + for (i = 0; i < 3; i++) { > + rc = read_multireg64(drvdata, awe_msx[chn][i], &data); > + if (rc < 0) > + return rc; > + > + p[i] = (u32)data; /* only use up to 30 bit here */ > + } > + > + 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 p_to_ms(u64 *ms, u32 *p) > +{ > + if (!p[0] && !p[1] && !p[2]) { > + /* uninitialized parameters in device */ > + ms[0] = 0; > + ms[1] = 0; > + ms[2] = 1; > + } else { > + /* c = p3 */ > + ms[2] = p[2]; > + /* b = (c * (p1 & 0x7f) + p2) >> 7 */ > + ms[1] = (ms[2] * (p[0] & 0x7f) + p[1]) >> 7; > + /* a = (p1 >> 7) + 4 */ > + ms[0] = (p[0] >> 7) + 4; > + } > + pr_debug("ms[]=%llu + %llu/%llu, p=%u %u %u\n", > + ms[0], ms[1], ms[2], p[0], p[1], p[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_p(struct si5338_driver_data *drvdata, > + u32 *p, int chn) > +{ > + int i, rc, hs = 0; > + > + rc = _verify_ms_channel(&drvdata->client->dev, chn); > + if (rc < 0) > + return rc; > + > + /* high speed bit programming */ > + if (p[0] < 512) { /* div less than 8 */ > + if (p[0] < 128) > + p[0] = 0; > + else > + p[0] = 256; > + p[1] = 0; > + p[2] = 1; > + hs = 1; > + dev_dbg(&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)p[i], > + awe_msx[chn][i]); > + if (rc < 0) > + return rc; > + } > + > + return 0; > +} > + > +/* > + * Calculate parameters > + * ms = ms[0] + ms[1] / ms[2] > + * > + * SI5338 RM stats the fomula of parameters as > + * p[0] = floor(((ms[0] * ms[2] + ms[1]) * 128) / ms[2] - 512) > + * p[1] = mod((ms[1] * 128), ms[2]) > + * p[2] = ms[2] > + */ > +static int ms_to_p(u64 *ms, u32 *p) > +{ > + 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)) { > + ms_denom >>= 1; > + ms_num >>= 1; > + } > + if (!ms_num || !ms_denom) { > + ms_denom = 1; > + ms_num = 0; > + } > + d = (ms_int * ms_denom + ms_num) << 7; > + p[0] = (u32)(div64_u64(d, ms_denom) - 512); > + d = div64_u64((ms_num << 7), ms_denom); > + p[1] = (u32)((ms_num << 7) - d * ms_denom); > + p[2] = ms_denom; > + pr_debug("ms[]=%llu + %llu/%llu Hz, ms_int=%llu, ms_num=%llu, > ms_denom=%llu p=%u %u %u\n", > + ms[0], ms[1], ms[2], ms_int, ms_num, ms_denom, > + p[0], p[1], p[2]); > + > + return 0; > +} > + > +/* > + * Calculate MultiSynth divider (MS0..MS3) for specified output > frequency > + */ > +static void cal_ms_p(unsigned long numerator, > + unsigned long denominator, > + u32 *p) > +{ > + 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_warn("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_warn("MSN ratio %llu is invalid\n", ms[0]); > + ms[0] += 1; > + } else if (ms[0] > MSINT_MAX) { > + pr_warn("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_p(ms, p); > +} > + > +/* > + * Si5338 pll section > + */ > +static int si5338_pll_prepare(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(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; > + u8 vco_gain_rsel_bwsel; > + > + pll_in_freq = clk_hw_get_rate(clk_hw_get_parent(hw)); > + if (!pll_in_freq) { > + dev_err(&drvdata->client->dev, "Invalid input clock for > pll\n"); > + return -EINVAL; > + } > + if (!clk_get_rate(hw->clk)) { > + dev_err(&drvdata->client->dev, "Invalid clock rate for > pll\n"); > + return -EINVAL; > + } > + > + fvco_mhz = div64_u64(clk_hw_get_rate(hw), 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_warn(&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_warn(&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, pll_kphi, AWE_PLL_KPHI); > + if (rc < 0) > + return rc; > + > + vco_gain_rsel_bwsel = (vco_gain & 7) << 4; > + vco_gain_rsel_bwsel |= (rsel & 3) << 2; > + vco_gain_rsel_bwsel |= bwsel & 3; > + rc = write_field(drvdata, vco_gain_rsel_bwsel, > AWE_VCO_GAIN_RSEL_BWSEL); > + if (rc < 0) > + return rc; > + > + rc = write_field(drvdata, mscal, AWE_MSCAL); > + if (rc < 0) > + return rc; > + > + rc = write_field(drvdata, 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 = HWDATA(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); > + if (pfd_in_ref < 0) { > + dev_err(&drvdata->client->dev, > + "Error getting pfd_in_ref mux\n"); > + /* In case reading register fails, set to 0 */ > + pfd_in_ref = 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 = HWDATA(hw); > + > + 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 = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + int rc; > + u64 rate[3], ms[3], ms_scaled; > + > + if (!hwdata->params.valid) { > + rc = get_ms_p(drvdata, hwdata->params.p, 4); > + if (rc < 0) { > + dev_err(&drvdata->client->dev, > + "Error reading ms register\n"); > + return 0; > + } > + hwdata->params.valid = true; > + } > + > + p_to_ms(ms, hwdata->params.p); > + if (unlikely(!ms[2])) { > + /* > + * This should not happen. Instead of crashing the > system, > + * set divisor to 1 and let the calculation continue. > + */ > + dev_warn(&drvdata->client->dev, > + "Error %s calculating pll\n", __func__); > + ms[2] = 1; > + } > + ms_scaled = ms[0] * ms[2] + ms[1]; > + if (!ms_scaled) /* 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 rate[0]; > +} > + > +static long si5338_pll_round_rate(struct clk_hw *hw, unsigned long > rate, > + unsigned long *parent_rate) > +{ I think is a designs problem in clock subsystem. Description of the function round_rate is this. @round_rate: Given a target rate as input, returns the closest rate actually supported by the clock. The parent rate is an input/output parameter. If given rate is unsigned long and the return value is long, we have a problem. Return value can be an error value but can also be a 32 bit frequency value that can be huge enoguh to set MSB bit and if MSB bit is set, the return value is a negative value and will be considered error code. In drivers/clk/clk.c, in function clk_core_determine_round_nolock, on line 1199 is this sequence of code that checks if the value returned by round_rate function is negative: } else if (core->ops->round_rate) { rate = core->ops->round_rate(core->hw, req->rate, &req->best_parent_rate); if (rate < 0) return rate; In drivers/clk/clk.c, in function clk_calc_new_rates, on line 1780, is the next sequence of code that checks if clk_core_determine_round_nolock returns a negative value: ret = clk_core_determine_round_nolock(core, &req); if (ret < 0) return NULL; and... if function clk_calc_new_rates returns NULL, in function clk_core_set_rate_nolock from drivers/clk/clk.c, on line 2023, is the next sequence of code that evaluates NULL, returned by clk_calc_new_rates as -EINVAL top = clk_calc_new_rates(core, req_rate); if (!top) return -EINVAL; So... si5338_pll_round_rate can return 32 bit values like 2500000000 Hz who have MSB bit set and are interpreted as error codes. Have someone a suggestion to fix this issue? > + struct si5338_hw_data *hwdata = HWDATA(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_p(rate, *parent_rate, hwdata->params.p); > + hwdata->params.valid = true; > + > + p_to_ms(ms, hwdata->params.p); > + 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 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 = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + if (unlikely(rate < FVCOMIN)) > + rate = FVCOMIN; > + else if (unlikely(rate > FVCOMAX)) > + rate = FVCOMAX; > + cal_ms_p(rate, parent_rate, hwdata->params.p); > + hwdata->params.valid = true; > + > + return set_ms_p(drvdata, hwdata->params.p, 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; > + > + return write_field(drvdata, down, awe_ms_powerdown[chn]); > +} > + > +static int si5338_msynth_prepare(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + return set_ms_powerdown(drvdata, MS_POWER_UP, hwdata->num); > +} > + > +static void si5338_msynth_unprepare(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + set_ms_powerdown(drvdata, MS_POWER_DOWN, hwdata->num); > +} > + > +static unsigned long si5338_msynth_recalc_rate(struct clk_hw *hw, > + unsigned long > parent_rate) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + int rc; > + u64 rate[3], ms[3], ms_scaled; > + > + if (!hwdata->params.valid) { > + rc = get_ms_p(drvdata, hwdata->params.p, hwdata->num); > + if (rc < 0) > + return 0; > + hwdata->params.valid = true; > + } > + > + p_to_ms(ms, hwdata->params.p); > + if (unlikely(!ms[2])) { > + /* This should not happen */ > + dev_warn(&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) /* 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 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 = HWDATA(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_hw_get_rate( > + clk_hw_get_parent(clk_hw_get_parent(hw) > )); > + if (!pll_in_freq) { > + dev_err(&drvdata->client->dev, > + "Invalid input clock for MS%d\n", > + hwdata->num); > + return -EINVAL; > + } > + > + 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 || 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) { > + dev_warn(&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; > + if (!rate) { > + dev_err(&drvdata->client->dev, > + "Invalid rate for MS%d\n", hwdata- > >num); > + return -EINVAL; > + } > + 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_p(*parent_rate, rate, hwdata->params.p); > + 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 = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + if (!rate) > + rate = div64_u64(parent_rate + MSINT_MAX - 1, > MSINT_MAX); > + > + cal_ms_p(parent_rate, rate, hwdata->params.p); > + hwdata->params.valid = true; > + > + return set_ms_p(drvdata, hwdata->params.p, 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, > +}; > + > +/* > + * Si5338 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; > + > + return write_field(drvdata, dis, 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, typ, 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, 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, typ, 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, vdd, 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 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, typ, awe_drv_invert[chn]); > +} > + > +static const u32 awe_drv_powerdown[] = { > + AWE_DRV0_PDN, > + AWE_DRV1_PDN, > + AWE_DRV2_PDN, > + AWE_DRV3_PDN > +}; > + > +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; > + > + return write_field(drvdata, 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}, > + > + {}, > +}; > + > +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)) > + 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)) ? > MS_POWER_UP : > + MS_POWER > _DOWN, > + chn); > + if (rc < 0) > + return rc; > + } > + > + return 0; > +} > + > +static const u32 awe_fcal[] = { > + AWE_FCAL_07_00, > + AWE_FCAL_15_08, > + AWE_FCAL_17_16, > + 0 > +}; > + > +static const 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) > +{ > + int rc; > + > + dev_dbg(&drvdata->client->dev, "Resetting MS dividers"); > + /* SET MS RESET = 1 */ > + rc = write_field(drvdata, 1, AWE_MS_RESET); > + if (rc) > + return rc; > + > + /* Wait for 20ms */ > + msleep(20); > + > + /* SET MS RESET = 0 */ > + rc = write_field(drvdata, 0, AWE_MS_RESET); > + if (rc) > + 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) > + return rc; > + > + rc = write_field(drvdata, 0x1, AWE_MISC_106); > + if (rc) > + return rc; > + > + rc = write_field(drvdata, 0x1, AWE_MISC_116); > + if (rc) > + return rc; > + > + rc = write_field(drvdata, 0x1, AWE_MISC_42); > + if (rc) > + return rc; > + > + rc = write_field(drvdata, 0x0, AWE_MISC_06A); > + if (rc) > + return rc; > + > + rc = write_field(drvdata, 0x0, AWE_MISC_06B); > + if (rc) > + return rc; > + > + rc = write_field(drvdata, 0x0, AWE_MISC_28); > + if (rc) > + 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) > + return rc; > + > + /* setup miscelalneous registers */ > + rc = set_misc_registers(drvdata); > + if (rc) > + return rc; > + > + /* disable all outputs */ > + rc = write_field(drvdata, 1, AWE_OUT_ALL_DIS); > + if (rc) > + return rc; > + > + /* pause LOL */ > + rc = write_field(drvdata, 1, AWE_DIS_LOS); > + if (rc) > + return rc; > + > + /* clears outputs pll input/fb muxes to be set later */ > + for (chn = 0; chn < 4; chn++) { > + rc = set_ms_powerdown(drvdata, MS_POWER_DOWN, chn); > + if (rc) > + return rc; > + rc = set_out_disable(drvdata, OUT_DISABLE, chn); > + if (rc) > + return rc; > + } > + /* to be explicitly enabled if needed */ > + rc = set_in_pfd_ref_fb(drvdata, 5, 0); /* noclk */ > + if (rc) > + return rc; > + > + rc = set_in_pfd_ref_fb(drvdata, 5, 1); /* noclk */ > + if (rc) > + return rc; > + > + return 0; > +} > + > +#define INIT_TIMEOUT 10 /* max 10 loops */ > + > +/* See SI5338 RM for programming procedure */ > +static int post_init(struct si5338_driver_data *drvdata) > +{ > + int rc = 0, i, in_src, fb_src, ext_fb, check_los = 0; > + int timeout = INIT_TIMEOUT; > + u64 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)) > + break; /* inputs OK */ > + msleep(100); /* wait for 100ms before polling */ > + } > + > + if (i >= timeout) { > + dev_err(&drvdata->client->dev, > + "Timeout waiting for input clocks, status=0x%x, > mask=0x%x\n", > + rc, check_los); > + return -ETIMEDOUT; > + } > + 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); > + msleep(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)) > + break; /* alarms not set OK */ > + msleep(100); /* wait for 100ms before polling */ > + } > + 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 -ETIMEDOUT; > + } > + 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 */ > + rc = read_multireg64(drvdata, awe_fcal, &fcal); > + if (rc) > + return rc; > + > + rc = write_multireg64(drvdata, fcal, awe_fcal_ovrd); > + if (rc) > + 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) > + return rc; > + > + /* SET PLL to use FCAL values, set FCAL_OVRD_EN=1 */ > + rc = write_field(drvdata, 1, AWE_FCAL_OVRD_EN); > + if (rc) > + return rc; > + > + /* only needed if using down-spread. Won't hurt to do anyway */ > + rc = reset_ms(drvdata); > + if (rc) > + return rc; > + > + /* Enable all (enabled individually) outputs */ > + rc = write_field(drvdata, 0, AWE_OUT_ALL_DIS); > + if (rc) > + return rc; > + > + /* Clearing */ > + write_field(drvdata, 0, AWE_SOFT_RESET); > + > + /* Power up MS if used, otherwise power down */ > + rc = power_up_down_needed_ms(drvdata); > + if (rc) > + return rc; > + > + return 0; > +} > + > +static int si5338_clkout_prepare(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + int rc; > + > + rc = set_drv_powerdown(drvdata, DRV_POWERUP, hwdata->num); > + if (rc) { > + dev_err(&drvdata->client->dev, > + "Error power up clkout%d\n", hwdata->num); > + } > + dev_dbg(&drvdata->client->dev, "Clkout%d prepared\n", hwdata- > >num); > + > + return rc; > +} > + > +static int si5338_clkout_enable(struct clk_hw *hw) > +{ > + > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + int rc; > + > + rc = set_out_disable(drvdata, OUT_ENABLE, hwdata->num); > + if (rc) { > + dev_err(&drvdata->client->dev, > + "Error enabling clkout%d\n", hwdata->num); > + } > + dev_dbg(&drvdata->client->dev, "Clkout%d enabled\n", hwdata- > >num); > + > + return rc; > +} > + > +static void si5338_clkout_disable(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + set_out_disable(drvdata, OUT_DISABLE, hwdata->num); > + dev_dbg(&drvdata->client->dev, "Clkout%d disable\n", hwdata- > >num); > +} > + > +static void si5338_clkout_unprepare(struct clk_hw *hw) > +{ > + struct si5338_hw_data *hwdata = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + set_drv_powerdown(drvdata, DRV_POWERDOWN, hwdata->num); > + dev_dbg(&drvdata->client->dev, "Clkout%d unprepared\n", hwdata- > >num); > +} > + > +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 = HWDATA(hw); > + struct si5338_driver_data *drvdata = hwdata->drvdata; > + > + return get_out_mux(drvdata, hwdata->num); > +} > + > +static int si5338_clkout_set_parent(struct clk_hw *hw, u8 index) > +{ > + struct si5338_hw_data *hwdata = HWDATA(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 = HWDATA(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_warn(&drvdata->client->dev, > + "Error recalculating rate for clk%d\n", hwdata- > >num); > + } else { > + rate /= rc; > + } > + dev_dbg(&drvdata->client->dev, "Recalculated 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 = HWDATA(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_warn(&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 = HWDATA(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, > + .enable = si5338_clkout_enable, > + .disable = si5338_clkout_disable, > + .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, > +}; > + > +#ifdef CONFIG_DEBUG_FS > + > +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 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 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 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 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 int get_drv_trim(struct si5338_driver_data *drvdata, int chn) > +{ > + int rc; > + u64 data; > + > + rc = _verify_output_channel(chn); > + if (rc < 0) > + return rc; > + > + rc = read_multireg64(drvdata, awe_drv_trim[chn], &data); > + if (rc < 0) > + return rc; > + > + return data; /* 5-bit data */ > +} > + > +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 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]); > +} > + > +/* > + * Create debugfs files for status for each si5338 clkout > + */ > + > +static int clkout_status_show(struct seq_file *s, void *data) > +{ > + struct si5338_hw_data *clkout = s->private; > + struct si5338_driver_data *drvdata = clkout->drvdata; > + struct si5338_drv_t const *config; > + int i, j, 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 > + }; > + > + i = clkout->num; > + seq_printf(s, "%d: ", i); > + if (get_out_disable(drvdata, i)) { > + seq_puts(s, "disabled"); > + switch (get_drv_disabled_state(drvdata, i)) { > + case SI5338_OUT_DIS_HIZ: > + seq_puts(s, " (high-Z)\n"); > + break; > + case SI5338_OUT_DIS_LOW: > + seq_puts(s, " (low)\n"); > + break; > + case SI5338_OUT_DIS_HI: > + seq_puts(s, " (high)\n"); > + break; > + case SI5338_OUT_DIS_ALWAYS_ON: > + seq_puts(s, " (always on)\n"); > + break; > + } > + return 0; > + } > + > + seq_puts(s, "enabled "); > + 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++) { > + config = &drv_configs[j]; > + if (config->fmt != drv_type) > + continue; > + if (config->vdd != drv_vdd) > + continue; > + if (config->trim != drv_trim) > + continue; > + if (((config->invert >> 2) | drv_invert) != > + ((config->invert >> 2) | (config->invert & 3))) > + continue; > + > + seq_puts(s, drv_configs[j].description); > + match = 1; > + break; > + } > + > + if (!match) { > + seq_printf(s, "Invalid output configuration: type = %d, > vdd=%d, trim=%d, invert=%d\n", > + drv_type, drv_vdd, drv_trim, drv_invert); > + } > + > + seq_printf(s, ", R%d and out %d power %s", i, i, > + get_drv_powerdown(drvdata, i) ? "down" : "up"); > + seq_puts(s, ", Output route "); > + > + 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 = read_field(drvdata, AWE_FB_MUX); > + 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 = read_field(drvdata, AWE_IN_MUX); > + 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; > + } > + switch (src_group) { > + case 0: > + seq_printf(s, "IN%d", in_numbers[src]); > + break; > + case 1: > + seq_puts(s, "XO"); > + break; > + case 2: > + seq_printf(s, "MS%d", src); > + break; > + case 3: > + seq_puts(s, "No clock"); > + break; > + } > + > + if (out_src == 5 || out_src == 6) { > + seq_printf(s, " power %s", > + get_ms_powerdown(drvdata, i) ? "down" : > "up"); > + } > + > + seq_puts(s, "\n"); > + > + return 0; > +} > + > +static int clkout_status_open(struct inode *inode, struct file > *file) > +{ > + return single_open(file, clkout_status_show, inode->i_private); > +} > + > +static const struct file_operations clkout_status_fops = { > + .open = clkout_status_open, > + .read = seq_read, > + .llseek = seq_lseek, > + .release = single_release, > +}; > + > +static int register_debugfs_status(struct si5338_hw_data *clkout) > +{ > + struct dentry *d; > + > + d = clk_debugfs_add_file(&clkout->hw, "output_status", S_IRUGO, > + clkout, &clkout_status_fops); > + if (!d) > + return -ENOMEM; > + > + return 0; > +} > + > +#else > +static int register_debugfs_status(struct si5338_hw_data *clkout) > +{ > + return 0; > +} > +#endif /* CONFIG_DEBUG_FS */ > + > +/* > + * 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; > + > + if (np == NULL) > + return 0; > + > + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); > + if (!pdata) > + return -ENOMEM; > + > + /* 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); > + return -EINVAL; > + } > + } > + > + /* 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); > + return -EINVAL; > + } > + } > + > + /* 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); > + return -EINVAL; > + } > + } > + > + /* 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); > + return -EINVAL; > + } > + 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; > + } > + > + of_property_read_string(child, "name", > + &pdata->clkout[num].name); > + > + 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); > + } > + if (of_find_property(child, "enabled", NULL)) > + pdata->clkout[num].enabled = true; > + } > + /* Replace platform data with device tree */ > + 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; > +} > + > +#define STRNCAT_LENGTH (MAX_NAME_LENGTH - name_prefix_length) > +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_LENGTH]; > + char register_name[MAX_NAME_LENGTH]; > + const char *pclkin[4] = {"in12", "in3", "in4", "in56"}; > + 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, name_prefix_length; > + 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; > + if (!pdata->name_prefix) { > + strlcpy(drvdata->name_prefix, > + dev_name(&client->dev), MAX_NAME_PREFIX - 2); > + strncat(drvdata->name_prefix, "-", 1); > + } else { > + strlcpy(drvdata->name_prefix, > + pdata->name_prefix, MAX_NAME_PREFIX); > + } > + name_prefix_length = strlen(drvdata->name_prefix); > + > + /* 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) > + 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 (require_xtal) { > + dev_err(&client->dev, > + "Error in configuration: > IN1/IN2 and XTAL are mutually exclusive\n"); > + return -EINVAL; > + } > + drvdata->pclkin[0] = devm_clk_get(&client->dev, > + pclkin[0]); > + if (PTR_ERR(drvdata->pclkin[0]) == > -EPROBE_DEFER) > + return -EPROBE_DEFER; > + if (IS_ERR_OR_NULL(drvdata->pclkin[0])) { > + dev_err(&client->dev, > + "IN1/IN2 doesn't a have > source\n"); > + return -EINVAL; > + } > + break; > + case SI5338_REF_SRC_CLKIN3: > + drvdata->pclkin[1] = devm_clk_get(&client->dev, > + pclkin[1]); > + if (PTR_ERR(drvdata->pclkin[1]) == > -EPROBE_DEFER) > + return -EPROBE_DEFER; > + if (IS_ERR_OR_NULL(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: > + drvdata->pclkin[2] = devm_clk_get(&client->dev, > + pclkin[2]); > + if (PTR_ERR(drvdata->pclkin[2]) == > -EPROBE_DEFER) > + return -EPROBE_DEFER; > + if (IS_ERR_OR_NULL(drvdata->pclkin[2])) { > + dev_err(&client->dev, > + "IN4 doesn't have a source\n"); > + return -EINVAL; > + } > + break; > + case SI5338_FB_SRC_CLKIN56: > + drvdata->pclkin[3] = devm_clk_get(&client->dev, > + pclkin[3]); > + if (PTR_ERR(drvdata->pclkin[3]) == > -EPROBE_DEFER) > + return -EPROBE_DEFER; > + if (IS_ERR_OR_NULL(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) { > + drvdata->pxtal = devm_clk_get(&client->dev, "xtal"); > + if (PTR_ERR(drvdata->pxtal) == -EPROBE_DEFER) > + return -EPROBE_DEFER; > + if (IS_ERR_OR_NULL(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 0x%x: > 0x%x, expected 0x%x. It is not %s\n", > + REG5338_DEV_CONFIG2, n, > REG5338_DEV_CONFIG2_VAL, > + id->name); > + return -ENODEV; > + } > + > + dev_dbg(&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; > + } > + } > + > + /* > + * To form clock names, concatentate name prefix with each > name. > + * The result string is up to MAX_NAME_LENGTH including > termination. > + */ > + > + /* Register xtal input clock */ > + if (!IS_ERR_OR_NULL(drvdata->pxtal)) { > + strlcpy(register_name, drvdata->name_prefix, > MAX_NAME_PREFIX); > + strncat(register_name, si5338_input_names[4], > STRNCAT_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_OR_NULL(drvdata->pclkin[n])) > + continue; > + > + drvdata->clkin[n].drvdata = drvdata; > + drvdata->clkin[n].num = n; > + strlcpy(register_name, drvdata->name_prefix, > MAX_NAME_PREFIX); > + strncat(register_name, si5338_input_names[n], > STRNCAT_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; > + 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], STRNCAT_LENGTH); > + strncat(name_buf[1], si5338_input_names[1], STRNCAT_LENGTH); > + strncat(name_buf[2], si5338_input_names[4], STRNCAT_LENGTH); > + strncat(name_buf[3], si5338_input_names[5], STRNCAT_LENGTH); > + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); > + strncat(register_name, si5338_pll_src_names[0], > STRNCAT_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 */ > + strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX); > + strncat(name_buf[0], si5338_pll_src_names[0], STRNCAT_LENGTH); > + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); > + strncat(register_name, si5338_pll_src_names[2], > STRNCAT_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; > + 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], STRNCAT_LENGTH); > + strncat(name_buf[1], si5338_input_names[3], STRNCAT_LENGTH); > + strncat(name_buf[2], si5338_input_names[5], STRNCAT_LENGTH); > + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); > + strncat(register_name, si5338_pll_src_names[1], > STRNCAT_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 */ > + strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX); > + strncat(name_buf[0], si5338_pll_src_names[1], STRNCAT_LENGTH); > + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); > + strncat(register_name, si5338_pll_src_names[3], > STRNCAT_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; > + 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], > STRNCAT_LENGTH); > + } > + strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX); > + strncat(register_name, si5338_msynth_src_names[0], > STRNCAT_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) { > + dev_err(&client->dev, "Cannot set pll > vco rate : %d\n", > + ret); > + 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); > + if (!drvdata->msynth) > + return -ENOMEM; > + > + drvdata->clkout = devm_kzalloc(&client->dev, 4 * > + sizeof(*drvdata->clkout), > GFP_KERNEL); > + if (!drvdata->clkout) > + return -ENOMEM; > + > + drvdata->onecell.clk_num = 4; > + drvdata->onecell.clks = devm_kzalloc(&client->dev, > + 4 * sizeof(*drvdata->onecell.clks), GFP_KERNEL); > + > + if (!drvdata->onecell.clks) > + return -ENOMEM; > + > + for (n = 0; n < 4; n++) { > + drvdata->msynth[n].num = n; > + drvdata->msynth[n].drvdata = drvdata; > + strlcpy(name_buf[0], drvdata->name_prefix, > MAX_NAME_PREFIX); > + strncat(name_buf[0], si5338_msynth_src_names[0], > + STRNCAT_LENGTH); > + strlcpy(register_name, drvdata->name_prefix, > MAX_NAME_PREFIX); > + strncat(register_name, si5338_msynth_names[n], > STRNCAT_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 > + */ > + 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], > + STRNCAT_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], > STRNCAT_LENGTH); > + /* > + * Use clkout_name from device tree or platform data > ignoring > + * name_prefix. The clkout_name must be unique for each > clock. > + */ > + if (pdata->clkout[n].name) { > + if (strlen(pdata->clkout[n].name) >= > MAX_NAME_LENGTH) { > + dev_warn(&client->dev, > + "clkout[%d] name %s too > long\n", > + n, pdata->clkout[n].name); > + } > + strlcpy(register_name, pdata->clkout[n].name, > + MAX_NAME_LENGTH); > + } else { > + strlcpy(register_name, drvdata->name_prefix, > + MAX_NAME_PREFIX); > + strncat(register_name, si5338_clkout_names[n], > + STRNCAT_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); > + > + if (register_debugfs_status(&drvdata->clkout[n])) { > + dev_warn(&client->dev, > + "Failed to register clkout status in > debugfs\n"); > + } > + > + drvdata->onecell.clks[n] = clk; > + > + /* set initial clkout rate */ > + if (pdata->clkout[n].rate) { > + 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) { > + dev_err(&client->dev, > + "Cannot set rate for clkout%d: > %d\n", > + n, ret); > + return ret; > + } > + /* clocks need to be prepared before post init > */ > + ret = clk_prepare(clk); > + if (ret) { > + dev_err(&client->dev, > + "Cannot prepare clk%d\n", n); > + return ret; > + } > + } > + } > + > + /* > + * 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; > + > + for (n = 0; n < 4; n++) { > + if (pdata->clkout[n].rate) { > + if (pdata->clkout[n].enabled) { > + ret = clk_enable(drvdata- > >onecell.clks[n]); > + if (ret) > + return ret; > + } else { > + clk_unprepare(drvdata- > >onecell.clks[n]); > + } > + } > + } > + > + dev_dbg(&client->dev, "%s clocks are registered\n", id->name); > + > +#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_warn(&client->dev, > + "Unable to add clkout%d to clkdev\n", > n); > + continue; > + } > + if (strlen(drvdata->lookup[n]->con_id) != > + strlen(__clk_get_name(clk))) { > + dev_warn(&client->dev, > + "Warning: clkdev doesn't support name > longer than %zu\n", > + strlen(drvdata->lookup[n]->con_id)); > + } > + 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; > + > +#ifdef CONFIG_OF > + of_clk_del_provider(client->dev.of_node); > +#endif > + > + for (n = 0; n < 4; n++) { > + if (drvdata->lookup[n]) > + clkdev_drop(drvdata->lookup[n]); > + } > + > + dev_dbg(&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@freescale.com"); > +MODULE_DESCRIPTION("Silicon Labs Si5338 clock generator driver"); > +MODULE_LICENSE("GPL v2"); > 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@freescale.com> > + * > + * This program is free software; you can redistribute it and/or > modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed "as is" WITHOUT ANY WARRANTY of any > + * kind, whether express or implied; without even the implied > warranty > + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#ifndef _DT_BINDINGS_CLK_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..5422955 > --- /dev/null > +++ b/include/linux/platform_data/si5338.h > @@ -0,0 +1,48 @@ > +/* > + * 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 > + * @name: clkout name. If omitted, clkout0/1/2/3 with name_prefix > will be used > + * @clkout_src: clkout source clock > + * @drive: output drive strength > + * @rate: initial clkout rate, or default if 0 > + * @enabled: output enabled by default > + */ > +struct si5338_clkout_config { > + const char *name; > + u8 clkout_src; > + const char *drive; > + u8 disable_state; > + unsigned long rate; > + bool enabled; > +}; > + > +/* > + * 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 > + * @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; > + 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] 6+ messages in thread
* Re: [EXT] Re: [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 2019-05-31 14:06 ` Radu Nicolae Pirea @ 2019-05-31 16:47 ` York Sun 2019-06-06 18:05 ` Stephen Boyd 1 sibling, 0 replies; 6+ messages in thread From: York Sun @ 2019-05-31 16:47 UTC (permalink / raw) To: Radu Nicolae Pirea, linux-clk@vger.kernel.org Cc: Mike Turquette, Sebastian Hesselbarth, Guenter Roeck, Andrey Filippov, Paul Bolle, Stephen Boyd, Rob Herring, Mark Rutland, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org On 5/31/19 7:06 AM, Radu Nicolae Pirea wrote: > Caution: EXT Email > > Hi, > > @York I want to continue the work on this driver and I want to upstream > it. Are you OK with this? > > I saw later improvement suggestions related to the bindings and I will > make the changes. Radu, You are welcome to improve this driver. York ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 2019-05-31 14:06 ` Radu Nicolae Pirea 2019-05-31 16:47 ` [EXT] " York Sun @ 2019-06-06 18:05 ` Stephen Boyd 1 sibling, 0 replies; 6+ messages in thread From: Stephen Boyd @ 2019-06-06 18:05 UTC (permalink / raw) To: Radu Nicolae Pirea, York Sun, linux-clk Cc: York Sun, Mike Turquette, Sebastian Hesselbarth, Guenter Roeck, Andrey Filippov, Paul Bolle, Stephen Boyd, Rob Herring, Mark Rutland, devicetree, linux-kernel Quoting Radu Nicolae Pirea (2019-05-31 07:06:03) > > + 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 rate[0]; > > +} > > + > > +static long si5338_pll_round_rate(struct clk_hw *hw, unsigned long > > rate, > > + unsigned long *parent_rate) > > +{ > I think is a designs problem in clock subsystem. > Description of the function round_rate is this. > > @round_rate: Given a target rate as input, returns the closest rate > actually supported by the clock. The parent rate is an input/output > parameter. > > If given rate is unsigned long and the return value is long, we have a > problem. Return value can be an error value but can also be a 32 bit > frequency value that can be huge enoguh to set MSB bit and if MSB bit > is set, the return value is a negative value and will be considered > error code. > > In drivers/clk/clk.c, in function clk_core_determine_round_nolock, on > line 1199 is this sequence of code that checks if the value returned by > round_rate function is negative: > > } else if (core->ops->round_rate) { > rate = core->ops->round_rate(core->hw, req->rate, > &req->best_parent_rate); > if (rate < 0) > return rate; > > In drivers/clk/clk.c, in function clk_calc_new_rates, on line 1780, is > the next sequence of code that checks if > clk_core_determine_round_nolock returns a negative value: > > ret = clk_core_determine_round_nolock(core, &req); > if (ret < 0) > return NULL; > > and... if function clk_calc_new_rates returns NULL, in function > clk_core_set_rate_nolock from drivers/clk/clk.c, on line 2023, is the > next sequence of code that evaluates NULL, returned by > clk_calc_new_rates as -EINVAL > > top = clk_calc_new_rates(core, req_rate); > if (!top) > return -EINVAL; > > So... si5338_pll_round_rate can return 32 bit values like 2500000000 Hz > who have MSB bit set and are interpreted as error codes. > > Have someone a suggestion to fix this issue? > Can you use the determine_rate clk op? It won't work for rounding outside of the framework though so things like clk_round_rate() still fail. ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2019-06-06 18:05 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2016-08-26 21:45 [Patch v9] driver/clk/clk-si5338: Add common clock framework driver for si5338 York Sun [not found] ` <1472247978-29312-1-git-send-email-york.sun-3arQi8VN3Tc@public.gmane.org> 2016-09-02 14:04 ` Rob Herring 2016-09-02 15:57 ` york sun 2019-05-31 14:06 ` Radu Nicolae Pirea 2019-05-31 16:47 ` [EXT] " York Sun 2019-06-06 18:05 ` Stephen Boyd
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).