aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk
diff options
context:
space:
mode:
authorRobert Hancock <robert.hancock@calian.com>2021-03-25 13:26:40 -0600
committerStephen Boyd <sboyd@kernel.org>2021-06-27 19:58:14 -0700
commitb7bbf6ec4940d1a69811ec354edeeb9751fa8e85 (patch)
tree174001b4779c82755e42665915ab81e73dc82b6a /drivers/clk
parentclk: si5341: Update initialization magic (diff)
downloadlinux-dev-b7bbf6ec4940d1a69811ec354edeeb9751fa8e85.tar.xz
linux-dev-b7bbf6ec4940d1a69811ec354edeeb9751fa8e85.zip
clk: si5341: Allow different output VDD_SEL values
The driver was not previously programming the VDD_SEL values for each output to indicate what external VDDO voltage was used for each. Add ability to specify a regulator supplying the VDDO pin for each output of the device. The voltage of the regulator is used to automatically set the VDD_SEL value appropriately. If no regulator is specified and the chip is being reconfigured, assume 2.5V which appears to be the chip default. Signed-off-by: Robert Hancock <robert.hancock@calian.com> Link: https://lore.kernel.org/r/20210325192643.2190069-7-robert.hancock@calian.com Signed-off-by: Stephen Boyd <sboyd@kernel.org>
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/clk-si5341.c136
1 files changed, 110 insertions, 26 deletions
diff --git a/drivers/clk/clk-si5341.c b/drivers/clk/clk-si5341.c
index eb22f4fdbc6b..28ac7085f362 100644
--- a/drivers/clk/clk-si5341.c
+++ b/drivers/clk/clk-si5341.c
@@ -19,6 +19,7 @@
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
@@ -59,6 +60,7 @@ struct clk_si5341_synth {
struct clk_si5341_output {
struct clk_hw hw;
struct clk_si5341 *data;
+ struct regulator *vddo_reg;
u8 index;
};
#define to_clk_si5341_output(_hw) \
@@ -84,6 +86,7 @@ struct clk_si5341 {
struct clk_si5341_output_config {
u8 out_format_drv_bits;
u8 out_cm_ampl_bits;
+ u8 vdd_sel_bits;
bool synth_master;
bool always_on;
};
@@ -136,6 +139,8 @@ struct clk_si5341_output_config {
#define SI5341_OUT_R_REG(output) \
((output)->data->reg_rdiv_offset[(output)->index])
+#define SI5341_OUT_MUX_VDD_SEL_MASK 0x38
+
/* Synthesize N divider */
#define SI5341_SYNTH_N_NUM(x) (0x0302 + ((x) * 11))
#define SI5341_SYNTH_N_DEN(x) (0x0308 + ((x) * 11))
@@ -1248,11 +1253,11 @@ static const struct regmap_config si5341_regmap_config = {
.volatile_table = &si5341_regmap_volatile,
};
-static int si5341_dt_parse_dt(struct i2c_client *client,
- struct clk_si5341_output_config *config)
+static int si5341_dt_parse_dt(struct clk_si5341 *data,
+ struct clk_si5341_output_config *config)
{
struct device_node *child;
- struct device_node *np = client->dev.of_node;
+ struct device_node *np = data->i2c_client->dev.of_node;
u32 num;
u32 val;
@@ -1261,13 +1266,13 @@ static int si5341_dt_parse_dt(struct i2c_client *client,
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",
+ dev_err(&data->i2c_client->dev, "missing reg property of %s\n",
child->name);
goto put_child;
}
if (num >= SI5341_MAX_NUM_OUTPUTS) {
- dev_err(&client->dev, "invalid clkout %d\n", num);
+ dev_err(&data->i2c_client->dev, "invalid clkout %d\n", num);
goto put_child;
}
@@ -1286,7 +1291,7 @@ static int si5341_dt_parse_dt(struct i2c_client *client,
config[num].out_format_drv_bits |= 0xc0;
break;
default:
- dev_err(&client->dev,
+ dev_err(&data->i2c_client->dev,
"invalid silabs,format %u for %u\n",
val, num);
goto put_child;
@@ -1299,7 +1304,7 @@ static int si5341_dt_parse_dt(struct i2c_client *client,
if (!of_property_read_u32(child, "silabs,common-mode", &val)) {
if (val > 0xf) {
- dev_err(&client->dev,
+ dev_err(&data->i2c_client->dev,
"invalid silabs,common-mode %u\n",
val);
goto put_child;
@@ -1310,7 +1315,7 @@ static int si5341_dt_parse_dt(struct i2c_client *client,
if (!of_property_read_u32(child, "silabs,amplitude", &val)) {
if (val > 0xf) {
- dev_err(&client->dev,
+ dev_err(&data->i2c_client->dev,
"invalid silabs,amplitude %u\n",
val);
goto put_child;
@@ -1327,6 +1332,34 @@ static int si5341_dt_parse_dt(struct i2c_client *client,
config[num].always_on =
of_property_read_bool(child, "always-on");
+
+ config[num].vdd_sel_bits = 0x08;
+ if (data->clk[num].vddo_reg) {
+ int vdd = regulator_get_voltage(data->clk[num].vddo_reg);
+
+ switch (vdd) {
+ case 3300000:
+ config[num].vdd_sel_bits |= 0 << 4;
+ break;
+ case 1800000:
+ config[num].vdd_sel_bits |= 1 << 4;
+ break;
+ case 2500000:
+ config[num].vdd_sel_bits |= 2 << 4;
+ break;
+ default:
+ dev_err(&data->i2c_client->dev,
+ "unsupported vddo voltage %d for %s\n",
+ vdd, child->name);
+ goto put_child;
+ }
+ } else {
+ /* chip seems to default to 2.5V when not set */
+ dev_warn(&data->i2c_client->dev,
+ "no regulator set, defaulting vdd_sel to 2.5V for %s\n",
+ child->name);
+ config[num].vdd_sel_bits |= 2 << 4;
+ }
}
return 0;
@@ -1452,9 +1485,33 @@ static int si5341_probe(struct i2c_client *client,
}
}
- err = si5341_dt_parse_dt(client, config);
+ for (i = 0; i < SI5341_MAX_NUM_OUTPUTS; ++i) {
+ char reg_name[10];
+
+ snprintf(reg_name, sizeof(reg_name), "vddo%d", i);
+ data->clk[i].vddo_reg = devm_regulator_get_optional(
+ &client->dev, reg_name);
+ if (IS_ERR(data->clk[i].vddo_reg)) {
+ err = PTR_ERR(data->clk[i].vddo_reg);
+ data->clk[i].vddo_reg = NULL;
+ if (err == -ENODEV)
+ continue;
+ goto cleanup;
+ } else {
+ err = regulator_enable(data->clk[i].vddo_reg);
+ if (err) {
+ dev_err(&client->dev,
+ "failed to enable %s regulator: %d\n",
+ reg_name, err);
+ data->clk[i].vddo_reg = NULL;
+ goto cleanup;
+ }
+ }
+ }
+
+ err = si5341_dt_parse_dt(data, config);
if (err)
- return err;
+ goto cleanup;
if (of_property_read_string(client->dev.of_node, "clock-output-names",
&init.name))
@@ -1462,21 +1519,23 @@ static int si5341_probe(struct i2c_client *client,
root_clock_name = init.name;
data->regmap = devm_regmap_init_i2c(client, &si5341_regmap_config);
- if (IS_ERR(data->regmap))
- return PTR_ERR(data->regmap);
+ if (IS_ERR(data->regmap)) {
+ err = PTR_ERR(data->regmap);
+ goto cleanup;
+ }
i2c_set_clientdata(client, data);
err = si5341_probe_chip_id(data);
if (err < 0)
- return err;
+ goto cleanup;
if (of_property_read_bool(client->dev.of_node, "silabs,reprogram")) {
initialization_required = true;
} else {
err = si5341_is_programmed_already(data);
if (err < 0)
- return err;
+ goto cleanup;
initialization_required = !err;
}
@@ -1485,11 +1544,11 @@ static int si5341_probe(struct i2c_client *client,
/* Populate the regmap cache in preparation for "cache only" */
err = si5341_read_settings(data);
if (err < 0)
- return err;
+ goto cleanup;
err = si5341_send_preamble(data);
if (err < 0)
- return err;
+ goto cleanup;
/*
* We intend to send all 'final' register values in a single
@@ -1502,19 +1561,19 @@ static int si5341_probe(struct i2c_client *client,
err = si5341_write_multiple(data, si5341_reg_defaults,
ARRAY_SIZE(si5341_reg_defaults));
if (err < 0)
- return err;
+ goto cleanup;
}
/* Input must be up and running at this point */
err = si5341_clk_select_active_input(data);
if (err < 0)
- return err;
+ goto cleanup;
if (initialization_required) {
/* PLL configuration is required */
err = si5341_initialize_pll(data);
if (err < 0)
- return err;
+ goto cleanup;
}
/* Register the PLL */
@@ -1527,7 +1586,7 @@ static int si5341_probe(struct i2c_client *client,
err = devm_clk_hw_register(&client->dev, &data->hw);
if (err) {
dev_err(&client->dev, "clock registration failed\n");
- return err;
+ goto cleanup;
}
init.num_parents = 1;
@@ -1564,13 +1623,17 @@ static int si5341_probe(struct i2c_client *client,
regmap_write(data->regmap,
SI5341_OUT_CM(&data->clk[i]),
config[i].out_cm_ampl_bits);
+ regmap_update_bits(data->regmap,
+ SI5341_OUT_MUX_SEL(&data->clk[i]),
+ SI5341_OUT_MUX_VDD_SEL_MASK,
+ config[i].vdd_sel_bits);
}
err = devm_clk_hw_register(&client->dev, &data->clk[i].hw);
kfree(init.name); /* clock framework made a copy of the name */
if (err) {
dev_err(&client->dev,
"output %u registration failed\n", i);
- return err;
+ goto cleanup;
}
if (config[i].always_on)
clk_prepare(data->clk[i].hw.clk);
@@ -1580,7 +1643,7 @@ static int si5341_probe(struct i2c_client *client,
data);
if (err) {
dev_err(&client->dev, "unable to add clk provider\n");
- return err;
+ goto cleanup;
}
if (initialization_required) {
@@ -1588,11 +1651,11 @@ static int si5341_probe(struct i2c_client *client,
regcache_cache_only(data->regmap, false);
err = regcache_sync(data->regmap);
if (err < 0)
- return err;
+ goto cleanup;
err = si5341_finalize_defaults(data);
if (err < 0)
- return err;
+ goto cleanup;
}
/* wait for device to report input clock present and PLL lock */
@@ -1601,14 +1664,14 @@ static int si5341_probe(struct i2c_client *client,
10000, 250000);
if (err) {
dev_err(&client->dev, "Error waiting for input clock or PLL lock\n");
- return err;
+ goto cleanup;
}
/* clear sticky alarm bits from initialization */
err = regmap_write(data->regmap, SI5341_STATUS_STICKY, 0);
if (err) {
dev_err(&client->dev, "unable to clear sticky status\n");
- return err;
+ goto cleanup;
}
/* Free the names, clk framework makes copies */
@@ -1616,6 +1679,26 @@ static int si5341_probe(struct i2c_client *client,
devm_kfree(&client->dev, (void *)synth_clock_names[i]);
return 0;
+
+cleanup:
+ for (i = 0; i < SI5341_MAX_NUM_OUTPUTS; ++i) {
+ if (data->clk[i].vddo_reg)
+ regulator_disable(data->clk[i].vddo_reg);
+ }
+ return err;
+}
+
+static int si5341_remove(struct i2c_client *client)
+{
+ struct clk_si5341 *data = i2c_get_clientdata(client);
+ int i;
+
+ for (i = 0; i < SI5341_MAX_NUM_OUTPUTS; ++i) {
+ if (data->clk[i].vddo_reg)
+ regulator_disable(data->clk[i].vddo_reg);
+ }
+
+ return 0;
}
static const struct i2c_device_id si5341_id[] = {
@@ -1644,6 +1727,7 @@ static struct i2c_driver si5341_driver = {
.of_match_table = clk_si5341_of_match,
},
.probe = si5341_probe,
+ .remove = si5341_remove,
.id_table = si5341_id,
};
module_i2c_driver(si5341_driver);