9abd5f0555
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> Tested-by: Michal Bachraty <michal.bachraty@streamunlimited.com> Signed-off-by: Mike Turquette <mturquette@linaro.org>
1510 lines
39 KiB
C
1510 lines
39 KiB
C
/*
|
|
* 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");
|