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