From: zonque@gmail.com (Daniel Mack)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH] clk: add si5351 i2c common clock driver
Date: Tue, 19 Feb 2013 20:15:39 +0100 [thread overview]
Message-ID: <5123CF5B.9060804@gmail.com> (raw)
In-Reply-To: <1360414772-12232-1-git-send-email-sebastian.hesselbarth@gmail.com>
Hi Sebastian,
I did some more tests today and it took me a while to dig for the root
cause why things were not working for me in the first place - see below.
On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
> +==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 at 60 {
> + compatible = "silabs,si5351a-msop";
> + reg = <0x60>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + #clock-cells = <1>;
> +
> + /* connect xtal input to 25MHz reference */
> + clocks = <&ref25>;
As referred to in another thread, registering the ref25M clock that way
didn't suffice for me on my platform - but that's a different story.
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> + unsigned char reg, struct si5351_parameters *params)
> +{
> + unsigned char buf[SI5351_PARAMETERS_LENGTH];
On a general note, I think you can use u8 instead of unsigned char all
over the place here, which will save you some indentation trouble.
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> + unsigned char num, unsigned char parent)
> +{
> + struct clk *pclk;
> +
> + if (num > 8 ||
> + (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> + return -EINVAL;
> +
> + switch (parent) {
> + case 0:
> + pclk = drvdata->msynth[num].hw.clk;
> + break;
> + case 1:
> + pclk = drvdata->msynth[0].hw.clk;
> + if (num >= 4)
> + pclk = drvdata->msynth[4].hw.clk;
> + break;
> + case 2:
> + pclk = drvdata->xtal.clk;
> + break;
> + case 3:
> + if (drvdata->variant != SI5351_VARIANT_C)
> + return -EINVAL;
> + pclk = drvdata->clkin.clk;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
[...]
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned val;
> +
> + val = 0;
> + hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> + switch (index) {
> + case 0:
> + hw->clk->flags |= CLK_SET_RATE_PARENT;
> + val = SI5351_CLK_INPUT_MULTISYNTH_N;
> + break;
I fugured that _si5351_clkout_reparent() wouldn't actually call
->set_parent() on the clock, which leads to the fact that
CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
end leaf is actually supplied with a new rate, which leads to incorrect
effective clocks, depending on the current multisynth/pll configuration.
The reason for this is in clk_set_parent() itself, which bails if the
parent is already set to the passed value:
if (clk->parent == parent)
goto out;
I fixed that for now by explicitly setting the clock's parent to NULL
before calling clk_set_parent() in _si5351_clkout_reparent(), so the
calbacks are triggered. But there might be a nicer way, for example to
factor out the CLK_SET_RATE_PARENT handling to some function called from
_si5351_clkout_reparent() or so.
Anyway, with this hack in place along with the other details I mentioned
in my first mail, the driver seems to work for me now, which is great. I
will do more extensive tests later that week when I have access to
better scopes ...
Many thanks again,
Daniel
> + case 1:
> + /* clk0/clk4 can only connect to its own multisync */
> + if (hwdata->num == 0 || hwdata->num == 4)
> + val = SI5351_CLK_INPUT_MULTISYNTH_N;
> + else
> + val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> + break;
> + case 2:
> + val = SI5351_CLK_INPUT_XTAL;
> + break;
> + case 3:
> + val = SI5351_CLK_INPUT_CLKIN;
> + break;
> + }
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_INPUT_MASK, val);
> +
> + return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char reg = SI5351_CLK0_PARAMETERS +
> + (SI5351_PARAMETERS_LENGTH * hwdata->num);
> + unsigned char rdiv;
> +
> + rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
> + SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
> +
> + return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char rdiv;
> +
> + /* clkout6/7 can only handle output freqencies < 150MHz */
> + if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> + rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> + /* clkout freqency is 8kHz - 160MHz */
> + if (rate > SI5351_CLKOUT_MAX_FREQ)
> + rate = SI5351_CLKOUT_MAX_FREQ;
> + if (rate < SI5351_CLKOUT_MIN_FREQ)
> + rate = SI5351_CLKOUT_MIN_FREQ;
> +
> + /* request frequency if multisync master */
> + if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> + /* use r divider for frequencies below 1MHz */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> + rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> + rdiv += 1;
> + rate *= 2;
> + }
> + *parent_rate = rate;
> + } else {
> + unsigned long new_rate, new_err, err;
> +
> + /* round to closed rdiv */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + new_rate = *parent_rate;
> + err = abs(new_rate - rate);
> + do {
> + new_rate >>= 1;
> + new_err = abs(new_rate - rate);
> + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> + break;
> + rdiv++;
> + err = new_err;
> + } while (1);
> + }
> + rate = *parent_rate >> rdiv;
> +
> + dev_dbg(&hwdata->drvdata->client->dev,
> + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> + __func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> + rate);
> +
> + return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char reg = SI5351_CLK0_PARAMETERS +
> + (SI5351_PARAMETERS_LENGTH * hwdata->num);
> + unsigned long new_rate, new_err, err;
> + unsigned char rdiv;
> +
> + /* round to closed rdiv */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + new_rate = parent_rate;
> + err = abs(new_rate - rate);
> + do {
> + new_rate >>= 1;
> + new_err = abs(new_rate - rate);
> + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> + break;
> + rdiv++;
> + err = new_err;
> + } while (1);
> +
> + /* powerdown clkout */
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> + /* write output divider */
> + switch (hwdata->num) {
> + case 6:
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> + SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> + break;
> + case 7:
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> + SI5351_OUTPUT_CLK_DIV_MASK,
> + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> + break;
> + default:
> + si5351_set_bits(hwdata->drvdata, reg + 2,
> + SI5351_OUTPUT_CLK_DIV_MASK,
> + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> + }
> +
> + /* powerup clkout */
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_POWERDOWN, 0);
> +
> + dev_dbg(&hwdata->drvdata->client->dev,
> + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> + __func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> + .prepare = si5351_clkout_prepare,
> + .unprepare = si5351_clkout_unprepare,
> + .is_enabled = si5351_clkout_is_enabled,
> + .set_parent = si5351_clkout_set_parent,
> + .get_parent = si5351_clkout_get_parent,
> + .recalc_rate = si5351_clkout_recalc_rate,
> + .round_rate = si5351_clkout_round_rate,
> + .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> + struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> + struct device_node *np = client->dev.of_node;
> + struct property *prop;
> + const __be32 *p;
> + unsigned int num, val;
> +
> + if (np == NULL)
> + return;
> +
> + /*
> + * property pll-source : <num src>, [<..>]
> + * allow to selectively set pll source
> + */
> + of_property_for_each_u32(client->dev.of_node, "pll-source",
> + prop, p, num) {
> + if (num >= 2) {
> + dev_err(&client->dev,
> + "invalid pll %d on pll-source prop\n", num);
> + break;
> + }
> +
> + p = of_prop_next_u32(prop, p, &val);
> + if (!p)
> + break;
> +
> + if (_si5351_pll_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent pll %d to %d\n",
> + num, val);
> + }
> +
> + for_each_child_of_node(client->dev.of_node, np) {
> + if (of_property_read_u32(np, "reg", &num)) {
> + dev_err(&client->dev, "missing reg property of %s\n",
> + np->full_name);
> + continue;
> + }
> +
> + if (of_property_read_bool(np, "pll-master"))
> + _si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> + if (!of_property_read_u32(np, "drive-strength", &val)) {
> + if (_si5351_clkout_set_drive_strength(drvdata,
> + num, val))
> + dev_warn(&client->dev,
> + "unable to set drive strength of %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "multisynth-source", &val)) {
> + if (_si5351_msynth_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent multisynth %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "clock-source", &val)) {
> + if (_si5351_clkout_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent clockout %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "clock-frequency", &val))
> + clk_set_rate(drvdata->onecell.clks[num], val);
> + }
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> + { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> + { .compatible = "silabs,si5351a-msop",
> + .data = (void *)SI5351_VARIANT_A3, },
> + { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> + { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> + struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> + struct device_node *np = client->dev.of_node;
> + const struct of_device_id *match;
> +
> + if (np == NULL)
> + return -EINVAL;
> +
> + match = of_match_node(si5351_dt_ids, np);
> + if (match == NULL)
> + return -EINVAL;
> +
> + drvdata->variant = (enum si5351_variant)match->data;
> + drvdata->pxtal = of_clk_get(np, 0);
> + drvdata->pclkin = of_clk_get(np, 1);
> +
> + return 0;
> +}
> +
> +static int si5351_i2c_probe(
> + struct i2c_client *client, const struct i2c_device_id *id)
> +{
> + struct si5351_driver_data *drvdata;
> + struct clk_init_data init;
> + struct clk *clk;
> + const char *parent_names[4];
> + u8 num_parents, num_clocks;
> + int ret, n;
> +
> + drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> + GFP_KERNEL);
> + if (drvdata == NULL) {
> + dev_err(&client->dev, "unable to allocate driver data\n");
> + return -ENOMEM;
> + }
> +
> + ret = si5351_dt_parse(client, drvdata);
> + if (ret)
> + return ret;
> +
> + i2c_set_clientdata(client, drvdata);
> + drvdata->client = client;
> + drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> + if (IS_ERR(drvdata->regmap)) {
> + dev_err(&client->dev, "failed to allocate register map\n");
> + return PTR_ERR(drvdata->regmap);
> + }
> +
> + /* Disable interrupts */
> + si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> + /* Set disabled output drivers to drive low */
> + si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> + si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> + /* Ensure pll select is on XTAL for Si5351A/B */
> + if (drvdata->variant != SI5351_VARIANT_C)
> + si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> + SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> + /* register xtal input clock gate */
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_input_names[0];
> + init.ops = &si5351_xtal_ops;
> + init.flags = 0;
> + if (!IS_ERR(drvdata->pxtal)) {
> + init.parent_names = &drvdata->pxtal->name;
> + init.num_parents = 1;
> + }
> + drvdata->xtal.init = &init;
> + clk = devm_clk_register(&client->dev, &drvdata->xtal);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return PTR_ERR(clk);
> + }
> +
> + /* register clkin input clock gate */
> + if (drvdata->variant == SI5351_VARIANT_C) {
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_input_names[1];
> + init.ops = &si5351_clkin_ops;
> + if (!IS_ERR(drvdata->pclkin)) {
> + init.parent_names = &drvdata->pclkin->name;
> + init.num_parents = 1;
> + }
> + drvdata->clkin.init = &init;
> + clk = devm_clk_register(&client->dev, &drvdata->clkin);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return PTR_ERR(clk);
> + }
> + }
> +
> + /* Si5351C allows to mux either xtal or clkin to PLL input */
> + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> + parent_names[0] = si5351_input_names[0];
> + parent_names[1] = si5351_input_names[1];
> +
> + /* register PLLA */
> + drvdata->pll[0].num = 0;
> + drvdata->pll[0].drvdata = drvdata;
> + drvdata->pll[0].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_pll_names[0];
> + init.ops = &si5351_pll_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return -EINVAL;
> + }
> +
> + /* register PLLB or VXCO (Si5351B) */
> + drvdata->pll[1].num = 1;
> + drvdata->pll[1].drvdata = drvdata;
> + drvdata->pll[1].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + if (drvdata->variant == SI5351_VARIANT_B) {
> + init.name = si5351_pll_names[2];
> + init.ops = &si5351_vxco_ops;
> + init.flags = CLK_IS_ROOT;
> + init.parent_names = NULL;
> + init.num_parents = 0;
> + } else {
> + init.name = si5351_pll_names[1];
> + init.ops = &si5351_pll_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + }
> + clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return -EINVAL;
> + }
> +
> + /* register clk multisync and clk out divider */
> + num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> + parent_names[0] = si5351_pll_names[0];
> + if (drvdata->variant == SI5351_VARIANT_B)
> + parent_names[1] = si5351_pll_names[2];
> + else
> + parent_names[1] = si5351_pll_names[1];
> +
> + drvdata->msynth = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> + drvdata->clkout = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> + drvdata->onecell.clk_num = num_clocks;
> + drvdata->onecell.clks = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> + if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> + !drvdata->onecell.clks))
> + return -ENOMEM;
> +
> + for (n = 0; n < num_clocks; n++) {
> + drvdata->msynth[n].num = n;
> + drvdata->msynth[n].drvdata = drvdata;
> + drvdata->msynth[n].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_msynth_names[n];
> + init.ops = &si5351_msynth_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = 2;
> + clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return -EINVAL;
> + }
> + }
> +
> + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> + parent_names[2] = si5351_input_names[0];
> + parent_names[3] = si5351_input_names[1];
> + for (n = 0; n < num_clocks; n++) {
> + parent_names[0] = si5351_msynth_names[n];
> + parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> + si5351_msynth_names[4];
> +
> + drvdata->clkout[n].num = n;
> + drvdata->clkout[n].drvdata = drvdata;
> + drvdata->clkout[n].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_clkout_names[n];
> + init.ops = &si5351_clkout_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return -EINVAL;
> + }
> + drvdata->onecell.clks[n] = clk;
> + }
> +
> + /* setup clock setup from DT */
> + si5351_dt_setup(client, drvdata);
> +
> + of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> + &drvdata->onecell);
> +
> + dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> + return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> + i2c_set_clientdata(client, NULL);
> + return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> + { "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> + { "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> + .driver = {
> + .name = "si5351",
> + .of_match_table = si5351_dt_ids,
> + },
> + .probe = si5351_i2c_probe,
> + .remove = si5351_i2c_remove,
> + .id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> + return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> + i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <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_MASK (3<<0)
> +#define SI5351_CLK_DRIVE_2MA (0<<0)
> +#define SI5351_CLK_DRIVE_4MA (1<<0)
> +#define SI5351_CLK_DRIVE_6MA (2<<0)
> +#define SI5351_CLK_DRIVE_8MA (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE 24
> +#define SI5351_CLK7_4_DISABLE_STATE 25
> +#define SI5351_CLK_DISABLE_STATE_LOW 0
> +#define SI5351_CLK_DISABLE_STATE_HIGH 1
> +#define SI5351_CLK_DISABLE_STATE_FLOAT 2
> +#define SI5351_CLK_DISABLE_STATE_NEVER 3
> +
> +#define SI5351_PARAMETERS_LENGTH 8
> +#define SI5351_PLLA_PARAMETERS 26
> +#define SI5351_PLLB_PARAMETERS 34
> +#define SI5351_CLK0_PARAMETERS 42
> +#define SI5351_CLK1_PARAMETERS 50
> +#define SI5351_CLK2_PARAMETERS 58
> +#define SI5351_CLK3_PARAMETERS 66
> +#define SI5351_CLK4_PARAMETERS 74
> +#define SI5351_CLK5_PARAMETERS 82
> +#define SI5351_CLK6_PARAMETERS 90
> +#define SI5351_CLK7_PARAMETERS 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER 92
> +#define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4)
> +#define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0)
> +#define SI5351_OUTPUT_CLK_DIV_SHIFT 4
> +#define SI5351_OUTPUT_CLK_DIV6_SHIFT 0
> +#define SI5351_OUTPUT_CLK_DIV_1 0
> +#define SI5351_OUTPUT_CLK_DIV_2 1
> +#define SI5351_OUTPUT_CLK_DIV_4 2
> +#define SI5351_OUTPUT_CLK_DIV_8 3
> +#define SI5351_OUTPUT_CLK_DIV_16 4
> +#define SI5351_OUTPUT_CLK_DIV_32 5
> +#define SI5351_OUTPUT_CLK_DIV_64 6
> +#define SI5351_OUTPUT_CLK_DIV_128 7
> +#define SI5351_OUTPUT_CLK_DIVBY4 (3<<2)
> +
> +#define SI5351_SSC_PARAM0 149
> +#define SI5351_SSC_PARAM1 150
> +#define SI5351_SSC_PARAM2 151
> +#define SI5351_SSC_PARAM3 152
> +#define SI5351_SSC_PARAM4 153
> +#define SI5351_SSC_PARAM5 154
> +#define SI5351_SSC_PARAM6 155
> +#define SI5351_SSC_PARAM7 156
> +#define SI5351_SSC_PARAM8 157
> +#define SI5351_SSC_PARAM9 158
> +#define SI5351_SSC_PARAM10 159
> +#define SI5351_SSC_PARAM11 160
> +#define SI5351_SSC_PARAM12 161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW 162
> +#define SI5351_VXCO_PARAMETERS_MID 163
> +#define SI5351_VXCO_PARAMETERS_HIGH 164
> +
> +#define SI5351_CLK0_PHASE_OFFSET 165
> +#define SI5351_CLK1_PHASE_OFFSET 166
> +#define SI5351_CLK2_PHASE_OFFSET 167
> +#define SI5351_CLK3_PHASE_OFFSET 168
> +#define SI5351_CLK4_PHASE_OFFSET 169
> +#define SI5351_CLK5_PHASE_OFFSET 170
> +
> +#define SI5351_PLL_RESET 177
> +#define SI5351_PLL_RESET_B (1<<7)
> +#define SI5351_PLL_RESET_A (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD 183
> +#define SI5351_CRYSTAL_LOAD_MASK (3<<6)
> +#define SI5351_CRYSTAL_LOAD_6PF (1<<6)
> +#define SI5351_CRYSTAL_LOAD_8PF (2<<6)
> +#define SI5351_CRYSTAL_LOAD_10PF (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE 187
> +#define SI5351_CLKIN_ENABLE (1<<7)
> +#define SI5351_XTAL_ENABLE (1<<6)
> +#define SI5351_MULTISYNTH_ENABLE (1<<4)
> +
> +#endif
>
WARNING: multiple messages have this Message-ID (diff)
From: Daniel Mack <zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
To: Sebastian Hesselbarth
<sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: Mike Turquette
<mturquette-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
Russell King - ARM Linux
<linux-lFZ/pmaqli7XmaaqVzeoHQ@public.gmane.org>,
linux-doc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Rob Herring <rob.herring-bsGFqQB8/DxBDgjK7y7TUQ@public.gmane.org>,
Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>,
Dom Cobley <popcornmix-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
Stephen Warren <swarren-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>,
Andrew Morton
<akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b@public.gmane.org>,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
Subject: Re: [PATCH] clk: add si5351 i2c common clock driver
Date: Tue, 19 Feb 2013 20:15:39 +0100 [thread overview]
Message-ID: <5123CF5B.9060804@gmail.com> (raw)
In-Reply-To: <1360414772-12232-1-git-send-email-sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Hi Sebastian,
I did some more tests today and it took me a while to dig for the root
cause why things were not working for me in the first place - see below.
On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
> +==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>;
As referred to in another thread, registering the ref25M clock that way
didn't suffice for me on my platform - but that's a different story.
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> + unsigned char reg, struct si5351_parameters *params)
> +{
> + unsigned char buf[SI5351_PARAMETERS_LENGTH];
On a general note, I think you can use u8 instead of unsigned char all
over the place here, which will save you some indentation trouble.
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> + unsigned char num, unsigned char parent)
> +{
> + struct clk *pclk;
> +
> + if (num > 8 ||
> + (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> + return -EINVAL;
> +
> + switch (parent) {
> + case 0:
> + pclk = drvdata->msynth[num].hw.clk;
> + break;
> + case 1:
> + pclk = drvdata->msynth[0].hw.clk;
> + if (num >= 4)
> + pclk = drvdata->msynth[4].hw.clk;
> + break;
> + case 2:
> + pclk = drvdata->xtal.clk;
> + break;
> + case 3:
> + if (drvdata->variant != SI5351_VARIANT_C)
> + return -EINVAL;
> + pclk = drvdata->clkin.clk;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
[...]
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned val;
> +
> + val = 0;
> + hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> + switch (index) {
> + case 0:
> + hw->clk->flags |= CLK_SET_RATE_PARENT;
> + val = SI5351_CLK_INPUT_MULTISYNTH_N;
> + break;
I fugured that _si5351_clkout_reparent() wouldn't actually call
->set_parent() on the clock, which leads to the fact that
CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
end leaf is actually supplied with a new rate, which leads to incorrect
effective clocks, depending on the current multisynth/pll configuration.
The reason for this is in clk_set_parent() itself, which bails if the
parent is already set to the passed value:
if (clk->parent == parent)
goto out;
I fixed that for now by explicitly setting the clock's parent to NULL
before calling clk_set_parent() in _si5351_clkout_reparent(), so the
calbacks are triggered. But there might be a nicer way, for example to
factor out the CLK_SET_RATE_PARENT handling to some function called from
_si5351_clkout_reparent() or so.
Anyway, with this hack in place along with the other details I mentioned
in my first mail, the driver seems to work for me now, which is great. I
will do more extensive tests later that week when I have access to
better scopes ...
Many thanks again,
Daniel
> + case 1:
> + /* clk0/clk4 can only connect to its own multisync */
> + if (hwdata->num == 0 || hwdata->num == 4)
> + val = SI5351_CLK_INPUT_MULTISYNTH_N;
> + else
> + val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> + break;
> + case 2:
> + val = SI5351_CLK_INPUT_XTAL;
> + break;
> + case 3:
> + val = SI5351_CLK_INPUT_CLKIN;
> + break;
> + }
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_INPUT_MASK, val);
> +
> + return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char reg = SI5351_CLK0_PARAMETERS +
> + (SI5351_PARAMETERS_LENGTH * hwdata->num);
> + unsigned char rdiv;
> +
> + rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
> + SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
> +
> + return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char rdiv;
> +
> + /* clkout6/7 can only handle output freqencies < 150MHz */
> + if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> + rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> + /* clkout freqency is 8kHz - 160MHz */
> + if (rate > SI5351_CLKOUT_MAX_FREQ)
> + rate = SI5351_CLKOUT_MAX_FREQ;
> + if (rate < SI5351_CLKOUT_MIN_FREQ)
> + rate = SI5351_CLKOUT_MIN_FREQ;
> +
> + /* request frequency if multisync master */
> + if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> + /* use r divider for frequencies below 1MHz */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> + rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> + rdiv += 1;
> + rate *= 2;
> + }
> + *parent_rate = rate;
> + } else {
> + unsigned long new_rate, new_err, err;
> +
> + /* round to closed rdiv */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + new_rate = *parent_rate;
> + err = abs(new_rate - rate);
> + do {
> + new_rate >>= 1;
> + new_err = abs(new_rate - rate);
> + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> + break;
> + rdiv++;
> + err = new_err;
> + } while (1);
> + }
> + rate = *parent_rate >> rdiv;
> +
> + dev_dbg(&hwdata->drvdata->client->dev,
> + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> + __func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> + rate);
> +
> + return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char reg = SI5351_CLK0_PARAMETERS +
> + (SI5351_PARAMETERS_LENGTH * hwdata->num);
> + unsigned long new_rate, new_err, err;
> + unsigned char rdiv;
> +
> + /* round to closed rdiv */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + new_rate = parent_rate;
> + err = abs(new_rate - rate);
> + do {
> + new_rate >>= 1;
> + new_err = abs(new_rate - rate);
> + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> + break;
> + rdiv++;
> + err = new_err;
> + } while (1);
> +
> + /* powerdown clkout */
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> + /* write output divider */
> + switch (hwdata->num) {
> + case 6:
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> + SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> + break;
> + case 7:
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> + SI5351_OUTPUT_CLK_DIV_MASK,
> + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> + break;
> + default:
> + si5351_set_bits(hwdata->drvdata, reg + 2,
> + SI5351_OUTPUT_CLK_DIV_MASK,
> + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> + }
> +
> + /* powerup clkout */
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_POWERDOWN, 0);
> +
> + dev_dbg(&hwdata->drvdata->client->dev,
> + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> + __func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> + .prepare = si5351_clkout_prepare,
> + .unprepare = si5351_clkout_unprepare,
> + .is_enabled = si5351_clkout_is_enabled,
> + .set_parent = si5351_clkout_set_parent,
> + .get_parent = si5351_clkout_get_parent,
> + .recalc_rate = si5351_clkout_recalc_rate,
> + .round_rate = si5351_clkout_round_rate,
> + .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> + struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> + struct device_node *np = client->dev.of_node;
> + struct property *prop;
> + const __be32 *p;
> + unsigned int num, val;
> +
> + if (np == NULL)
> + return;
> +
> + /*
> + * property pll-source : <num src>, [<..>]
> + * allow to selectively set pll source
> + */
> + of_property_for_each_u32(client->dev.of_node, "pll-source",
> + prop, p, num) {
> + if (num >= 2) {
> + dev_err(&client->dev,
> + "invalid pll %d on pll-source prop\n", num);
> + break;
> + }
> +
> + p = of_prop_next_u32(prop, p, &val);
> + if (!p)
> + break;
> +
> + if (_si5351_pll_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent pll %d to %d\n",
> + num, val);
> + }
> +
> + for_each_child_of_node(client->dev.of_node, np) {
> + if (of_property_read_u32(np, "reg", &num)) {
> + dev_err(&client->dev, "missing reg property of %s\n",
> + np->full_name);
> + continue;
> + }
> +
> + if (of_property_read_bool(np, "pll-master"))
> + _si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> + if (!of_property_read_u32(np, "drive-strength", &val)) {
> + if (_si5351_clkout_set_drive_strength(drvdata,
> + num, val))
> + dev_warn(&client->dev,
> + "unable to set drive strength of %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "multisynth-source", &val)) {
> + if (_si5351_msynth_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent multisynth %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "clock-source", &val)) {
> + if (_si5351_clkout_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent clockout %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "clock-frequency", &val))
> + clk_set_rate(drvdata->onecell.clks[num], val);
> + }
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> + { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> + { .compatible = "silabs,si5351a-msop",
> + .data = (void *)SI5351_VARIANT_A3, },
> + { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> + { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> + struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> + struct device_node *np = client->dev.of_node;
> + const struct of_device_id *match;
> +
> + if (np == NULL)
> + return -EINVAL;
> +
> + match = of_match_node(si5351_dt_ids, np);
> + if (match == NULL)
> + return -EINVAL;
> +
> + drvdata->variant = (enum si5351_variant)match->data;
> + drvdata->pxtal = of_clk_get(np, 0);
> + drvdata->pclkin = of_clk_get(np, 1);
> +
> + return 0;
> +}
> +
> +static int si5351_i2c_probe(
> + struct i2c_client *client, const struct i2c_device_id *id)
> +{
> + struct si5351_driver_data *drvdata;
> + struct clk_init_data init;
> + struct clk *clk;
> + const char *parent_names[4];
> + u8 num_parents, num_clocks;
> + int ret, n;
> +
> + drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> + GFP_KERNEL);
> + if (drvdata == NULL) {
> + dev_err(&client->dev, "unable to allocate driver data\n");
> + return -ENOMEM;
> + }
> +
> + ret = si5351_dt_parse(client, drvdata);
> + if (ret)
> + return ret;
> +
> + i2c_set_clientdata(client, drvdata);
> + drvdata->client = client;
> + drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> + if (IS_ERR(drvdata->regmap)) {
> + dev_err(&client->dev, "failed to allocate register map\n");
> + return PTR_ERR(drvdata->regmap);
> + }
> +
> + /* Disable interrupts */
> + si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> + /* Set disabled output drivers to drive low */
> + si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> + si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> + /* Ensure pll select is on XTAL for Si5351A/B */
> + if (drvdata->variant != SI5351_VARIANT_C)
> + si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> + SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> + /* register xtal input clock gate */
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_input_names[0];
> + init.ops = &si5351_xtal_ops;
> + init.flags = 0;
> + if (!IS_ERR(drvdata->pxtal)) {
> + init.parent_names = &drvdata->pxtal->name;
> + init.num_parents = 1;
> + }
> + drvdata->xtal.init = &init;
> + clk = devm_clk_register(&client->dev, &drvdata->xtal);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return PTR_ERR(clk);
> + }
> +
> + /* register clkin input clock gate */
> + if (drvdata->variant == SI5351_VARIANT_C) {
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_input_names[1];
> + init.ops = &si5351_clkin_ops;
> + if (!IS_ERR(drvdata->pclkin)) {
> + init.parent_names = &drvdata->pclkin->name;
> + init.num_parents = 1;
> + }
> + drvdata->clkin.init = &init;
> + clk = devm_clk_register(&client->dev, &drvdata->clkin);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return PTR_ERR(clk);
> + }
> + }
> +
> + /* Si5351C allows to mux either xtal or clkin to PLL input */
> + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> + parent_names[0] = si5351_input_names[0];
> + parent_names[1] = si5351_input_names[1];
> +
> + /* register PLLA */
> + drvdata->pll[0].num = 0;
> + drvdata->pll[0].drvdata = drvdata;
> + drvdata->pll[0].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_pll_names[0];
> + init.ops = &si5351_pll_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return -EINVAL;
> + }
> +
> + /* register PLLB or VXCO (Si5351B) */
> + drvdata->pll[1].num = 1;
> + drvdata->pll[1].drvdata = drvdata;
> + drvdata->pll[1].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + if (drvdata->variant == SI5351_VARIANT_B) {
> + init.name = si5351_pll_names[2];
> + init.ops = &si5351_vxco_ops;
> + init.flags = CLK_IS_ROOT;
> + init.parent_names = NULL;
> + init.num_parents = 0;
> + } else {
> + init.name = si5351_pll_names[1];
> + init.ops = &si5351_pll_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + }
> + clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return -EINVAL;
> + }
> +
> + /* register clk multisync and clk out divider */
> + num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> + parent_names[0] = si5351_pll_names[0];
> + if (drvdata->variant == SI5351_VARIANT_B)
> + parent_names[1] = si5351_pll_names[2];
> + else
> + parent_names[1] = si5351_pll_names[1];
> +
> + drvdata->msynth = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> + drvdata->clkout = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> + drvdata->onecell.clk_num = num_clocks;
> + drvdata->onecell.clks = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> + if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> + !drvdata->onecell.clks))
> + return -ENOMEM;
> +
> + for (n = 0; n < num_clocks; n++) {
> + drvdata->msynth[n].num = n;
> + drvdata->msynth[n].drvdata = drvdata;
> + drvdata->msynth[n].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_msynth_names[n];
> + init.ops = &si5351_msynth_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = 2;
> + clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return -EINVAL;
> + }
> + }
> +
> + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> + parent_names[2] = si5351_input_names[0];
> + parent_names[3] = si5351_input_names[1];
> + for (n = 0; n < num_clocks; n++) {
> + parent_names[0] = si5351_msynth_names[n];
> + parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> + si5351_msynth_names[4];
> +
> + drvdata->clkout[n].num = n;
> + drvdata->clkout[n].drvdata = drvdata;
> + drvdata->clkout[n].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_clkout_names[n];
> + init.ops = &si5351_clkout_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return -EINVAL;
> + }
> + drvdata->onecell.clks[n] = clk;
> + }
> +
> + /* setup clock setup from DT */
> + si5351_dt_setup(client, drvdata);
> +
> + of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> + &drvdata->onecell);
> +
> + dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> + return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> + i2c_set_clientdata(client, NULL);
> + return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> + { "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> + { "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> + .driver = {
> + .name = "si5351",
> + .of_match_table = si5351_dt_ids,
> + },
> + .probe = si5351_i2c_probe,
> + .remove = si5351_i2c_remove,
> + .id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> + return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> + i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth-EimvkypIwmU@public.gmane.org");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + * Rabeeh Khoury <rabeeh-UBr1pzP51AyaMJb+Lgu22Q@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
> + */
> +
> +#ifndef _CLK_SI5351_H_
> +#define _CLK_SI5351_H_
> +
> +#define SI5351_BUS_BASE_ADDR 0x60
> +
> +#define SI5351_PLL_VCO_MIN 600000000
> +#define SI5351_PLL_VCO_MAX 900000000
> +#define SI5351_MULTISYNTH_MIN_FREQ 1000000
> +#define SI5351_MULTISYNTH_DIVBY4_FREQ 150000000
> +#define SI5351_MULTISYNTH_MAX_FREQ 160000000
> +#define SI5351_MULTISYNTH67_MAX_FREQ SI5351_MULTISYNTH_DIVBY4_FREQ
> +#define SI5351_CLKOUT_MIN_FREQ 8000
> +#define SI5351_CLKOUT_MAX_FREQ SI5351_MULTISYNTH_MAX_FREQ
> +#define SI5351_CLKOUT67_MAX_FREQ SI5351_MULTISYNTH67_MAX_FREQ
> +
> +#define SI5351_PLL_A_MIN 15
> +#define SI5351_PLL_A_MAX 90
> +#define SI5351_PLL_B_MAX (SI5351_PLL_C_MAX-1)
> +#define SI5351_PLL_C_MAX 1048575
> +#define SI5351_MULTISYNTH_A_MIN 6
> +#define SI5351_MULTISYNTH_A_MAX 1800
> +#define SI5351_MULTISYNTH67_A_MAX 254
> +#define SI5351_MULTISYNTH_B_MAX (SI5351_MULTISYNTH_C_MAX-1)
> +#define SI5351_MULTISYNTH_C_MAX 1048575
> +#define SI5351_MULTISYNTH_P1_MAX ((1<<18)-1)
> +#define SI5351_MULTISYNTH_P2_MAX ((1<<20)-1)
> +#define SI5351_MULTISYNTH_P3_MAX ((1<<20)-1)
> +
> +#define SI5351_DEVICE_STATUS 0
> +#define SI5351_INTERRUPT_STATUS 1
> +#define SI5351_INTERRUPT_MASK 2
> +#define SI5351_STATUS_SYS_INIT (1<<7)
> +#define SI5351_STATUS_LOL_B (1<<6)
> +#define SI5351_STATUS_LOL_A (1<<5)
> +#define SI5351_STATUS_LOS (1<<4)
> +#define SI5351_OUTPUT_ENABLE_CTRL 3
> +#define SI5351_OEB_PIN_ENABLE_CTRL 9
> +#define SI5351_PLL_INPUT_SOURCE 15
> +#define SI5351_CLKIN_DIV_MASK (3<<6)
> +#define SI5351_CLKIN_DIV_1 (0<<6)
> +#define SI5351_CLKIN_DIV_2 (1<<6)
> +#define SI5351_CLKIN_DIV_4 (2<<6)
> +#define SI5351_CLKIN_DIV_8 (3<<6)
> +#define SI5351_PLLB_SOURCE (1<<3)
> +#define SI5351_PLLA_SOURCE (1<<2)
> +
> +#define SI5351_CLK0_CTRL 16
> +#define SI5351_CLK1_CTRL 17
> +#define SI5351_CLK2_CTRL 18
> +#define SI5351_CLK3_CTRL 19
> +#define SI5351_CLK4_CTRL 20
> +#define SI5351_CLK5_CTRL 21
> +#define SI5351_CLK6_CTRL 22
> +#define SI5351_CLK7_CTRL 23
> +#define SI5351_CLK_POWERDOWN (1<<7)
> +#define SI5351_CLK_INTEGER_MODE (1<<6)
> +#define SI5351_CLK_PLL_SELECT (1<<5)
> +#define SI5351_CLK_INVERT (1<<4)
> +#define SI5351_CLK_INPUT_MASK (3<<2)
> +#define SI5351_CLK_INPUT_XTAL (0<<2)
> +#define SI5351_CLK_INPUT_CLKIN (1<<2)
> +#define SI5351_CLK_INPUT_MULTISYNTH_0_4 (2<<2)
> +#define SI5351_CLK_INPUT_MULTISYNTH_N (3<<2)
> +#define SI5351_CLK_DRIVE_MASK (3<<0)
> +#define SI5351_CLK_DRIVE_2MA (0<<0)
> +#define SI5351_CLK_DRIVE_4MA (1<<0)
> +#define SI5351_CLK_DRIVE_6MA (2<<0)
> +#define SI5351_CLK_DRIVE_8MA (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE 24
> +#define SI5351_CLK7_4_DISABLE_STATE 25
> +#define SI5351_CLK_DISABLE_STATE_LOW 0
> +#define SI5351_CLK_DISABLE_STATE_HIGH 1
> +#define SI5351_CLK_DISABLE_STATE_FLOAT 2
> +#define SI5351_CLK_DISABLE_STATE_NEVER 3
> +
> +#define SI5351_PARAMETERS_LENGTH 8
> +#define SI5351_PLLA_PARAMETERS 26
> +#define SI5351_PLLB_PARAMETERS 34
> +#define SI5351_CLK0_PARAMETERS 42
> +#define SI5351_CLK1_PARAMETERS 50
> +#define SI5351_CLK2_PARAMETERS 58
> +#define SI5351_CLK3_PARAMETERS 66
> +#define SI5351_CLK4_PARAMETERS 74
> +#define SI5351_CLK5_PARAMETERS 82
> +#define SI5351_CLK6_PARAMETERS 90
> +#define SI5351_CLK7_PARAMETERS 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER 92
> +#define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4)
> +#define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0)
> +#define SI5351_OUTPUT_CLK_DIV_SHIFT 4
> +#define SI5351_OUTPUT_CLK_DIV6_SHIFT 0
> +#define SI5351_OUTPUT_CLK_DIV_1 0
> +#define SI5351_OUTPUT_CLK_DIV_2 1
> +#define SI5351_OUTPUT_CLK_DIV_4 2
> +#define SI5351_OUTPUT_CLK_DIV_8 3
> +#define SI5351_OUTPUT_CLK_DIV_16 4
> +#define SI5351_OUTPUT_CLK_DIV_32 5
> +#define SI5351_OUTPUT_CLK_DIV_64 6
> +#define SI5351_OUTPUT_CLK_DIV_128 7
> +#define SI5351_OUTPUT_CLK_DIVBY4 (3<<2)
> +
> +#define SI5351_SSC_PARAM0 149
> +#define SI5351_SSC_PARAM1 150
> +#define SI5351_SSC_PARAM2 151
> +#define SI5351_SSC_PARAM3 152
> +#define SI5351_SSC_PARAM4 153
> +#define SI5351_SSC_PARAM5 154
> +#define SI5351_SSC_PARAM6 155
> +#define SI5351_SSC_PARAM7 156
> +#define SI5351_SSC_PARAM8 157
> +#define SI5351_SSC_PARAM9 158
> +#define SI5351_SSC_PARAM10 159
> +#define SI5351_SSC_PARAM11 160
> +#define SI5351_SSC_PARAM12 161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW 162
> +#define SI5351_VXCO_PARAMETERS_MID 163
> +#define SI5351_VXCO_PARAMETERS_HIGH 164
> +
> +#define SI5351_CLK0_PHASE_OFFSET 165
> +#define SI5351_CLK1_PHASE_OFFSET 166
> +#define SI5351_CLK2_PHASE_OFFSET 167
> +#define SI5351_CLK3_PHASE_OFFSET 168
> +#define SI5351_CLK4_PHASE_OFFSET 169
> +#define SI5351_CLK5_PHASE_OFFSET 170
> +
> +#define SI5351_PLL_RESET 177
> +#define SI5351_PLL_RESET_B (1<<7)
> +#define SI5351_PLL_RESET_A (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD 183
> +#define SI5351_CRYSTAL_LOAD_MASK (3<<6)
> +#define SI5351_CRYSTAL_LOAD_6PF (1<<6)
> +#define SI5351_CRYSTAL_LOAD_8PF (2<<6)
> +#define SI5351_CRYSTAL_LOAD_10PF (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE 187
> +#define SI5351_CLKIN_ENABLE (1<<7)
> +#define SI5351_XTAL_ENABLE (1<<6)
> +#define SI5351_MULTISYNTH_ENABLE (1<<4)
> +
> +#endif
>
WARNING: multiple messages have this Message-ID (diff)
From: Daniel Mack <zonque@gmail.com>
To: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Cc: Stephen Warren <swarren@nvidia.com>,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
Rob Herring <rob.herring@calxeda.com>,
Russell King - ARM Linux <linux@arm.linux.org.uk>,
Dom Cobley <popcornmix@gmail.com>,
Mike Turquette <mturquette@linaro.org>,
Andrew Morton <akpm@linux-foundation.org>,
Rabeeh Khoury <rabeeh@solid-run.com>,
devicetree-discuss@lists.ozlabs.org,
linux-arm-kernel@lists.infradead.org
Subject: Re: [PATCH] clk: add si5351 i2c common clock driver
Date: Tue, 19 Feb 2013 20:15:39 +0100 [thread overview]
Message-ID: <5123CF5B.9060804@gmail.com> (raw)
In-Reply-To: <1360414772-12232-1-git-send-email-sebastian.hesselbarth@gmail.com>
Hi Sebastian,
I did some more tests today and it took me a while to dig for the root
cause why things were not working for me in the first place - see below.
On 09.02.2013 13:59, Sebastian Hesselbarth wrote:
> +==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>;
As referred to in another thread, registering the ref25M clock that way
didn't suffice for me on my platform - but that's a different story.
> +static void si5351_read_parameters(struct si5351_driver_data *drvdata,
> + unsigned char reg, struct si5351_parameters *params)
> +{
> + unsigned char buf[SI5351_PARAMETERS_LENGTH];
On a general note, I think you can use u8 instead of unsigned char all
over the place here, which will save you some indentation trouble.
> +static inline int _si5351_clkout_reparent(struct si5351_driver_data *drvdata,
> + unsigned char num, unsigned char parent)
> +{
> + struct clk *pclk;
> +
> + if (num > 8 ||
> + (drvdata->variant == SI5351_VARIANT_A3 && num > 3))
> + return -EINVAL;
> +
> + switch (parent) {
> + case 0:
> + pclk = drvdata->msynth[num].hw.clk;
> + break;
> + case 1:
> + pclk = drvdata->msynth[0].hw.clk;
> + if (num >= 4)
> + pclk = drvdata->msynth[4].hw.clk;
> + break;
> + case 2:
> + pclk = drvdata->xtal.clk;
> + break;
> + case 3:
> + if (drvdata->variant != SI5351_VARIANT_C)
> + return -EINVAL;
> + pclk = drvdata->clkin.clk;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return clk_set_parent(drvdata->clkout[num].hw.clk, pclk);
> +}
[...]
> +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned val;
> +
> + val = 0;
> + hw->clk->flags &= ~CLK_SET_RATE_PARENT;
> + switch (index) {
> + case 0:
> + hw->clk->flags |= CLK_SET_RATE_PARENT;
> + val = SI5351_CLK_INPUT_MULTISYNTH_N;
> + break;
I fugured that _si5351_clkout_reparent() wouldn't actually call
->set_parent() on the clock, which leads to the fact that
CLK_SET_RATE_PARENT is not set in the flags. That way, only the clkout
end leaf is actually supplied with a new rate, which leads to incorrect
effective clocks, depending on the current multisynth/pll configuration.
The reason for this is in clk_set_parent() itself, which bails if the
parent is already set to the passed value:
if (clk->parent == parent)
goto out;
I fixed that for now by explicitly setting the clock's parent to NULL
before calling clk_set_parent() in _si5351_clkout_reparent(), so the
calbacks are triggered. But there might be a nicer way, for example to
factor out the CLK_SET_RATE_PARENT handling to some function called from
_si5351_clkout_reparent() or so.
Anyway, with this hack in place along with the other details I mentioned
in my first mail, the driver seems to work for me now, which is great. I
will do more extensive tests later that week when I have access to
better scopes ...
Many thanks again,
Daniel
> + case 1:
> + /* clk0/clk4 can only connect to its own multisync */
> + if (hwdata->num == 0 || hwdata->num == 4)
> + val = SI5351_CLK_INPUT_MULTISYNTH_N;
> + else
> + val = SI5351_CLK_INPUT_MULTISYNTH_0_4;
> + break;
> + case 2:
> + val = SI5351_CLK_INPUT_XTAL;
> + break;
> + case 3:
> + val = SI5351_CLK_INPUT_CLKIN;
> + break;
> + }
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_INPUT_MASK, val);
> +
> + return 0;
> +}
> +
> +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char reg = SI5351_CLK0_PARAMETERS +
> + (SI5351_PARAMETERS_LENGTH * hwdata->num);
> + unsigned char rdiv;
> +
> + rdiv = (si5351_reg_read(hwdata->drvdata, reg + 2) &
> + SI5351_OUTPUT_CLK_DIV_MASK) >> SI5351_OUTPUT_CLK_DIV_SHIFT;
> +
> + return parent_rate >> rdiv;
> +}
> +
> +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char rdiv;
> +
> + /* clkout6/7 can only handle output freqencies < 150MHz */
> + if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ)
> + rate = SI5351_CLKOUT67_MAX_FREQ;
> +
> + /* clkout freqency is 8kHz - 160MHz */
> + if (rate > SI5351_CLKOUT_MAX_FREQ)
> + rate = SI5351_CLKOUT_MAX_FREQ;
> + if (rate < SI5351_CLKOUT_MIN_FREQ)
> + rate = SI5351_CLKOUT_MIN_FREQ;
> +
> + /* request frequency if multisync master */
> + if (hwdata->hw.clk->flags & CLK_SET_RATE_PARENT) {
> + /* use r divider for frequencies below 1MHz */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + while (rate < SI5351_MULTISYNTH_MIN_FREQ &&
> + rdiv < SI5351_OUTPUT_CLK_DIV_128) {
> + rdiv += 1;
> + rate *= 2;
> + }
> + *parent_rate = rate;
> + } else {
> + unsigned long new_rate, new_err, err;
> +
> + /* round to closed rdiv */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + new_rate = *parent_rate;
> + err = abs(new_rate - rate);
> + do {
> + new_rate >>= 1;
> + new_err = abs(new_rate - rate);
> + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> + break;
> + rdiv++;
> + err = new_err;
> + } while (1);
> + }
> + rate = *parent_rate >> rdiv;
> +
> + dev_dbg(&hwdata->drvdata->client->dev,
> + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> + __func__, hwdata->hw.clk->name, (1 << rdiv), *parent_rate,
> + rate);
> +
> + return rate;
> +}
> +
> +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct si5351_hw_data *hwdata =
> + container_of(hw, struct si5351_hw_data, hw);
> + unsigned char reg = SI5351_CLK0_PARAMETERS +
> + (SI5351_PARAMETERS_LENGTH * hwdata->num);
> + unsigned long new_rate, new_err, err;
> + unsigned char rdiv;
> +
> + /* round to closed rdiv */
> + rdiv = SI5351_OUTPUT_CLK_DIV_1;
> + new_rate = parent_rate;
> + err = abs(new_rate - rate);
> + do {
> + new_rate >>= 1;
> + new_err = abs(new_rate - rate);
> + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128)
> + break;
> + rdiv++;
> + err = new_err;
> + } while (1);
> +
> + /* powerdown clkout */
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN);
> +
> + /* write output divider */
> + switch (hwdata->num) {
> + case 6:
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> + SI5351_OUTPUT_CLK6_DIV_MASK, rdiv);
> + break;
> + case 7:
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER,
> + SI5351_OUTPUT_CLK_DIV_MASK,
> + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> + break;
> + default:
> + si5351_set_bits(hwdata->drvdata, reg + 2,
> + SI5351_OUTPUT_CLK_DIV_MASK,
> + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT);
> + }
> +
> + /* powerup clkout */
> + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num,
> + SI5351_CLK_POWERDOWN, 0);
> +
> + dev_dbg(&hwdata->drvdata->client->dev,
> + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n",
> + __func__, hwdata->hw.clk->name, (1 << rdiv), parent_rate, rate);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops si5351_clkout_ops = {
> + .prepare = si5351_clkout_prepare,
> + .unprepare = si5351_clkout_unprepare,
> + .is_enabled = si5351_clkout_is_enabled,
> + .set_parent = si5351_clkout_set_parent,
> + .get_parent = si5351_clkout_get_parent,
> + .recalc_rate = si5351_clkout_recalc_rate,
> + .round_rate = si5351_clkout_round_rate,
> + .set_rate = si5351_clkout_set_rate,
> +};
> +
> +/*
> + * Si5351 i2c probe and DT
> + */
> +static void si5351_dt_setup(
> + struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> + struct device_node *np = client->dev.of_node;
> + struct property *prop;
> + const __be32 *p;
> + unsigned int num, val;
> +
> + if (np == NULL)
> + return;
> +
> + /*
> + * property pll-source : <num src>, [<..>]
> + * allow to selectively set pll source
> + */
> + of_property_for_each_u32(client->dev.of_node, "pll-source",
> + prop, p, num) {
> + if (num >= 2) {
> + dev_err(&client->dev,
> + "invalid pll %d on pll-source prop\n", num);
> + break;
> + }
> +
> + p = of_prop_next_u32(prop, p, &val);
> + if (!p)
> + break;
> +
> + if (_si5351_pll_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent pll %d to %d\n",
> + num, val);
> + }
> +
> + for_each_child_of_node(client->dev.of_node, np) {
> + if (of_property_read_u32(np, "reg", &num)) {
> + dev_err(&client->dev, "missing reg property of %s\n",
> + np->full_name);
> + continue;
> + }
> +
> + if (of_property_read_bool(np, "pll-master"))
> + _si5351_msynth_set_pll_master(drvdata, num, 1);
> +
> + if (!of_property_read_u32(np, "drive-strength", &val)) {
> + if (_si5351_clkout_set_drive_strength(drvdata,
> + num, val))
> + dev_warn(&client->dev,
> + "unable to set drive strength of %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "multisynth-source", &val)) {
> + if (_si5351_msynth_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent multisynth %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "clock-source", &val)) {
> + if (_si5351_clkout_reparent(drvdata, num, val))
> + dev_warn(&client->dev,
> + "unable to reparent clockout %d to %d\n",
> + num, val);
> + }
> +
> + if (!of_property_read_u32(np, "clock-frequency", &val))
> + clk_set_rate(drvdata->onecell.clks[num], val);
> + }
> +}
> +
> +static const struct of_device_id si5351_dt_ids[] = {
> + { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, },
> + { .compatible = "silabs,si5351a-msop",
> + .data = (void *)SI5351_VARIANT_A3, },
> + { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, },
> + { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_dt_ids);
> +
> +static int si5351_dt_parse(
> + struct i2c_client *client, struct si5351_driver_data *drvdata)
> +{
> + struct device_node *np = client->dev.of_node;
> + const struct of_device_id *match;
> +
> + if (np == NULL)
> + return -EINVAL;
> +
> + match = of_match_node(si5351_dt_ids, np);
> + if (match == NULL)
> + return -EINVAL;
> +
> + drvdata->variant = (enum si5351_variant)match->data;
> + drvdata->pxtal = of_clk_get(np, 0);
> + drvdata->pclkin = of_clk_get(np, 1);
> +
> + return 0;
> +}
> +
> +static int si5351_i2c_probe(
> + struct i2c_client *client, const struct i2c_device_id *id)
> +{
> + struct si5351_driver_data *drvdata;
> + struct clk_init_data init;
> + struct clk *clk;
> + const char *parent_names[4];
> + u8 num_parents, num_clocks;
> + int ret, n;
> +
> + drvdata = devm_kzalloc(&client->dev, sizeof(struct si5351_driver_data),
> + GFP_KERNEL);
> + if (drvdata == NULL) {
> + dev_err(&client->dev, "unable to allocate driver data\n");
> + return -ENOMEM;
> + }
> +
> + ret = si5351_dt_parse(client, drvdata);
> + if (ret)
> + return ret;
> +
> + i2c_set_clientdata(client, drvdata);
> + drvdata->client = client;
> + drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config);
> + if (IS_ERR(drvdata->regmap)) {
> + dev_err(&client->dev, "failed to allocate register map\n");
> + return PTR_ERR(drvdata->regmap);
> + }
> +
> + /* Disable interrupts */
> + si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0);
> + /* Set disabled output drivers to drive low */
> + si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00);
> + si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00);
> + /* Ensure pll select is on XTAL for Si5351A/B */
> + if (drvdata->variant != SI5351_VARIANT_C)
> + si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE,
> + SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0);
> +
> + /* register xtal input clock gate */
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_input_names[0];
> + init.ops = &si5351_xtal_ops;
> + init.flags = 0;
> + if (!IS_ERR(drvdata->pxtal)) {
> + init.parent_names = &drvdata->pxtal->name;
> + init.num_parents = 1;
> + }
> + drvdata->xtal.init = &init;
> + clk = devm_clk_register(&client->dev, &drvdata->xtal);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return PTR_ERR(clk);
> + }
> +
> + /* register clkin input clock gate */
> + if (drvdata->variant == SI5351_VARIANT_C) {
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_input_names[1];
> + init.ops = &si5351_clkin_ops;
> + if (!IS_ERR(drvdata->pclkin)) {
> + init.parent_names = &drvdata->pclkin->name;
> + init.num_parents = 1;
> + }
> + drvdata->clkin.init = &init;
> + clk = devm_clk_register(&client->dev, &drvdata->clkin);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return PTR_ERR(clk);
> + }
> + }
> +
> + /* Si5351C allows to mux either xtal or clkin to PLL input */
> + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1;
> + parent_names[0] = si5351_input_names[0];
> + parent_names[1] = si5351_input_names[1];
> +
> + /* register PLLA */
> + drvdata->pll[0].num = 0;
> + drvdata->pll[0].drvdata = drvdata;
> + drvdata->pll[0].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_pll_names[0];
> + init.ops = &si5351_pll_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return -EINVAL;
> + }
> +
> + /* register PLLB or VXCO (Si5351B) */
> + drvdata->pll[1].num = 1;
> + drvdata->pll[1].drvdata = drvdata;
> + drvdata->pll[1].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + if (drvdata->variant == SI5351_VARIANT_B) {
> + init.name = si5351_pll_names[2];
> + init.ops = &si5351_vxco_ops;
> + init.flags = CLK_IS_ROOT;
> + init.parent_names = NULL;
> + init.num_parents = 0;
> + } else {
> + init.name = si5351_pll_names[1];
> + init.ops = &si5351_pll_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + }
> + clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n", init.name);
> + return -EINVAL;
> + }
> +
> + /* register clk multisync and clk out divider */
> + num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8;
> + parent_names[0] = si5351_pll_names[0];
> + if (drvdata->variant == SI5351_VARIANT_B)
> + parent_names[1] = si5351_pll_names[2];
> + else
> + parent_names[1] = si5351_pll_names[1];
> +
> + drvdata->msynth = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> + drvdata->clkout = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct si5351_hw_data), GFP_KERNEL);
> +
> + drvdata->onecell.clk_num = num_clocks;
> + drvdata->onecell.clks = devm_kzalloc(&client->dev,
> + num_clocks * sizeof(struct clk *), GFP_KERNEL);
> +
> + if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
> + !drvdata->onecell.clks))
> + return -ENOMEM;
> +
> + for (n = 0; n < num_clocks; n++) {
> + drvdata->msynth[n].num = n;
> + drvdata->msynth[n].drvdata = drvdata;
> + drvdata->msynth[n].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_msynth_names[n];
> + init.ops = &si5351_msynth_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = 2;
> + clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return -EINVAL;
> + }
> + }
> +
> + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3;
> + parent_names[2] = si5351_input_names[0];
> + parent_names[3] = si5351_input_names[1];
> + for (n = 0; n < num_clocks; n++) {
> + parent_names[0] = si5351_msynth_names[n];
> + parent_names[1] = (n < 4) ? si5351_msynth_names[0] :
> + si5351_msynth_names[4];
> +
> + drvdata->clkout[n].num = n;
> + drvdata->clkout[n].drvdata = drvdata;
> + drvdata->clkout[n].hw.init = &init;
> + memset(&init, 0, sizeof(struct clk_init_data));
> + init.name = si5351_clkout_names[n];
> + init.ops = &si5351_clkout_ops;
> + init.flags = 0;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw);
> + if (IS_ERR(clk)) {
> + dev_err(&client->dev, "unable to register %s\n",
> + init.name);
> + return -EINVAL;
> + }
> + drvdata->onecell.clks[n] = clk;
> + }
> +
> + /* setup clock setup from DT */
> + si5351_dt_setup(client, drvdata);
> +
> + of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get,
> + &drvdata->onecell);
> +
> + dev_info(&client->dev, "registered si5351 i2c client\n");
> +
> + return 0;
> +}
> +
> +static int si5351_i2c_remove(struct i2c_client *client)
> +{
> + i2c_set_clientdata(client, NULL);
> + return 0;
> +}
> +
> +static const struct i2c_device_id si5351_i2c_ids[] = {
> + { "silabs,si5351", SI5351_BUS_BASE_ADDR | 0 },
> + { "silabs,si5351", SI5351_BUS_BASE_ADDR | 1 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids);
> +
> +static struct i2c_driver si5351_driver = {
> + .driver = {
> + .name = "si5351",
> + .of_match_table = si5351_dt_ids,
> + },
> + .probe = si5351_i2c_probe,
> + .remove = si5351_i2c_remove,
> + .id_table = si5351_i2c_ids,
> +};
> +
> +static int __init si5351_module_init(void)
> +{
> + return i2c_add_driver(&si5351_driver);
> +}
> +module_init(si5351_module_init);
> +
> +static void __exit si5351_module_exit(void)
> +{
> + i2c_del_driver(&si5351_driver);
> +}
> +module_exit(si5351_module_exit);
> +
> +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.de");
> +MODULE_DESCRIPTION("Silicon Labs Si5351A/B/C clock generator driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h
> new file mode 100644
> index 0000000..424073c
> --- /dev/null
> +++ b/drivers/clk/clk-si5351.h
> @@ -0,0 +1,155 @@
> +/*
> + * clk-si5351.h: Silicon Laboratories Si5351A/B/C I2C Clock Generator
> + *
> + * Sebastian Hesselbarth <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_MASK (3<<0)
> +#define SI5351_CLK_DRIVE_2MA (0<<0)
> +#define SI5351_CLK_DRIVE_4MA (1<<0)
> +#define SI5351_CLK_DRIVE_6MA (2<<0)
> +#define SI5351_CLK_DRIVE_8MA (3<<0)
> +
> +#define SI5351_CLK3_0_DISABLE_STATE 24
> +#define SI5351_CLK7_4_DISABLE_STATE 25
> +#define SI5351_CLK_DISABLE_STATE_LOW 0
> +#define SI5351_CLK_DISABLE_STATE_HIGH 1
> +#define SI5351_CLK_DISABLE_STATE_FLOAT 2
> +#define SI5351_CLK_DISABLE_STATE_NEVER 3
> +
> +#define SI5351_PARAMETERS_LENGTH 8
> +#define SI5351_PLLA_PARAMETERS 26
> +#define SI5351_PLLB_PARAMETERS 34
> +#define SI5351_CLK0_PARAMETERS 42
> +#define SI5351_CLK1_PARAMETERS 50
> +#define SI5351_CLK2_PARAMETERS 58
> +#define SI5351_CLK3_PARAMETERS 66
> +#define SI5351_CLK4_PARAMETERS 74
> +#define SI5351_CLK5_PARAMETERS 82
> +#define SI5351_CLK6_PARAMETERS 90
> +#define SI5351_CLK7_PARAMETERS 91
> +#define SI5351_CLK6_7_OUTPUT_DIVIDER 92
> +#define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4)
> +#define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0)
> +#define SI5351_OUTPUT_CLK_DIV_SHIFT 4
> +#define SI5351_OUTPUT_CLK_DIV6_SHIFT 0
> +#define SI5351_OUTPUT_CLK_DIV_1 0
> +#define SI5351_OUTPUT_CLK_DIV_2 1
> +#define SI5351_OUTPUT_CLK_DIV_4 2
> +#define SI5351_OUTPUT_CLK_DIV_8 3
> +#define SI5351_OUTPUT_CLK_DIV_16 4
> +#define SI5351_OUTPUT_CLK_DIV_32 5
> +#define SI5351_OUTPUT_CLK_DIV_64 6
> +#define SI5351_OUTPUT_CLK_DIV_128 7
> +#define SI5351_OUTPUT_CLK_DIVBY4 (3<<2)
> +
> +#define SI5351_SSC_PARAM0 149
> +#define SI5351_SSC_PARAM1 150
> +#define SI5351_SSC_PARAM2 151
> +#define SI5351_SSC_PARAM3 152
> +#define SI5351_SSC_PARAM4 153
> +#define SI5351_SSC_PARAM5 154
> +#define SI5351_SSC_PARAM6 155
> +#define SI5351_SSC_PARAM7 156
> +#define SI5351_SSC_PARAM8 157
> +#define SI5351_SSC_PARAM9 158
> +#define SI5351_SSC_PARAM10 159
> +#define SI5351_SSC_PARAM11 160
> +#define SI5351_SSC_PARAM12 161
> +
> +#define SI5351_VXCO_PARAMETERS_LOW 162
> +#define SI5351_VXCO_PARAMETERS_MID 163
> +#define SI5351_VXCO_PARAMETERS_HIGH 164
> +
> +#define SI5351_CLK0_PHASE_OFFSET 165
> +#define SI5351_CLK1_PHASE_OFFSET 166
> +#define SI5351_CLK2_PHASE_OFFSET 167
> +#define SI5351_CLK3_PHASE_OFFSET 168
> +#define SI5351_CLK4_PHASE_OFFSET 169
> +#define SI5351_CLK5_PHASE_OFFSET 170
> +
> +#define SI5351_PLL_RESET 177
> +#define SI5351_PLL_RESET_B (1<<7)
> +#define SI5351_PLL_RESET_A (1<<5)
> +
> +#define SI5351_CRYSTAL_LOAD 183
> +#define SI5351_CRYSTAL_LOAD_MASK (3<<6)
> +#define SI5351_CRYSTAL_LOAD_6PF (1<<6)
> +#define SI5351_CRYSTAL_LOAD_8PF (2<<6)
> +#define SI5351_CRYSTAL_LOAD_10PF (3<<6)
> +
> +#define SI5351_FANOUT_ENABLE 187
> +#define SI5351_CLKIN_ENABLE (1<<7)
> +#define SI5351_XTAL_ENABLE (1<<6)
> +#define SI5351_MULTISYNTH_ENABLE (1<<4)
> +
> +#endif
>
next prev parent reply other threads:[~2013-02-19 19:15 UTC|newest]
Thread overview: 95+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-02-09 12:59 [PATCH] clk: add si5351 i2c common clock driver Sebastian Hesselbarth
2013-02-09 12:59 ` Sebastian Hesselbarth
2013-02-11 5:46 ` Mike Turquette
2013-02-11 5:46 ` Mike Turquette
2013-02-11 5:46 ` Mike Turquette
2013-02-11 9:52 ` Sebastian Hesselbarth
2013-02-11 9:52 ` Sebastian Hesselbarth
2013-02-18 10:19 ` Daniel Mack
2013-02-18 10:19 ` Daniel Mack
2013-02-19 19:15 ` Daniel Mack [this message]
2013-02-19 19:15 ` Daniel Mack
2013-02-19 19:15 ` Daniel Mack
2013-02-27 10:01 ` Sebastian Hesselbarth
2013-02-27 10:01 ` Sebastian Hesselbarth
2013-03-01 15:01 ` Daniel Mack
2013-03-01 15:01 ` Daniel Mack
2013-03-16 13:10 ` [PATCH v2] " Sebastian Hesselbarth
2013-03-16 13:10 ` Sebastian Hesselbarth
2013-03-16 15:10 ` Daniel Mack
2013-03-16 15:10 ` Daniel Mack
2013-03-18 10:43 ` [PATCH v3] " Sebastian Hesselbarth
2013-03-18 10:43 ` Sebastian Hesselbarth
2013-03-18 11:37 ` Daniel Mack
2013-03-18 11:37 ` Daniel Mack
2013-03-20 0:26 ` Mike Turquette
2013-03-20 0:26 ` Mike Turquette
2013-03-20 0:26 ` Mike Turquette
2013-03-20 8:20 ` Sebastian Hesselbarth
2013-03-20 8:20 ` Sebastian Hesselbarth
2013-03-21 18:09 ` Lars-Peter Clausen
2013-03-21 18:09 ` Lars-Peter Clausen
2013-03-21 21:32 ` Sebastian Hesselbarth
2013-03-21 21:32 ` Sebastian Hesselbarth
2013-03-23 10:07 ` Lars-Peter Clausen
2013-03-23 10:07 ` Lars-Peter Clausen
2013-03-23 14:46 ` [PATCH v4] " Sebastian Hesselbarth
2013-03-23 14:46 ` Sebastian Hesselbarth
2013-03-23 14:46 ` Sebastian Hesselbarth
2013-04-02 23:46 ` Mike Turquette
2013-04-02 23:46 ` Mike Turquette
2013-04-02 23:46 ` Mike Turquette
2013-04-03 11:10 ` Sebastian Hesselbarth
2013-04-03 11:10 ` Sebastian Hesselbarth
2013-04-05 5:23 ` [PATCH v5] " Sebastian Hesselbarth
2013-04-05 5:23 ` Sebastian Hesselbarth
2013-04-07 22:50 ` [v5] " Guenter Roeck
2013-04-07 22:50 ` Guenter Roeck
2013-04-07 23:49 ` Sebastian Hesselbarth
2013-04-07 23:49 ` Sebastian Hesselbarth
2013-04-08 0:17 ` Guenter Roeck
2013-04-08 0:17 ` Guenter Roeck
2013-04-08 6:11 ` Sebastian Hesselbarth
2013-04-08 6:11 ` Sebastian Hesselbarth
2013-04-08 14:54 ` Guenter Roeck
2013-04-08 14:54 ` Guenter Roeck
2013-04-08 14:54 ` Guenter Roeck
2013-04-08 15:38 ` Sebastian Hesselbarth
2013-04-08 15:38 ` Sebastian Hesselbarth
2013-04-08 15:38 ` Sebastian Hesselbarth
2013-04-08 17:36 ` Mike Turquette
2013-04-08 17:36 ` Mike Turquette
2013-04-08 18:32 ` Sebastian Hesselbarth
2013-04-08 18:32 ` Sebastian Hesselbarth
2013-04-08 16:46 ` [PATCH v6] " Sebastian Hesselbarth
2013-04-08 16:46 ` Sebastian Hesselbarth
2013-04-08 17:46 ` Guenter Roeck
2013-04-08 17:46 ` Guenter Roeck
2013-04-08 18:24 ` Sebastian Hesselbarth
2013-04-08 18:24 ` Sebastian Hesselbarth
2013-04-10 9:40 ` [PATCH v7] " Sebastian Hesselbarth
2013-04-10 9:40 ` Sebastian Hesselbarth
2013-04-10 9:40 ` Sebastian Hesselbarth
2013-04-10 10:17 ` Daniel Mack
2013-04-10 10:17 ` Daniel Mack
2013-04-10 14:48 ` Michal Bachraty
2013-04-10 14:48 ` Michal Bachraty
2013-04-10 16:34 ` Sebastian Hesselbarth
2013-04-10 17:27 ` Sebastian Hesselbarth
2013-04-10 17:27 ` Sebastian Hesselbarth
2013-04-10 17:27 ` Sebastian Hesselbarth
2013-04-11 7:44 ` Michal Bachraty
2013-04-11 7:44 ` Michal Bachraty
2013-04-11 8:22 ` Sebastian Hesselbarth
2013-04-11 8:22 ` Sebastian Hesselbarth
2013-04-10 19:34 ` Guenter Roeck
2013-04-10 19:34 ` Guenter Roeck
2013-04-11 19:42 ` [PATCH v8] " Sebastian Hesselbarth
2013-04-11 19:42 ` Sebastian Hesselbarth
2013-04-11 19:42 ` Sebastian Hesselbarth
2013-04-12 11:43 ` Michal Bachraty
2013-04-12 11:43 ` Michal Bachraty
2013-04-12 11:43 ` Michal Bachraty
2013-04-12 18:19 ` Mike Turquette
2013-04-12 18:19 ` Mike Turquette
2013-04-12 18:19 ` Mike Turquette
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5123CF5B.9060804@gmail.com \
--to=zonque@gmail.com \
--cc=linux-arm-kernel@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.