|
@@ -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))
|
|
@@ -1250,11 +1255,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;
|
|
|
|
|
@@ -1263,13 +1268,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;
|
|
|
}
|
|
|
|
|
@@ -1288,7 +1293,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;
|
|
@@ -1301,7 +1306,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;
|
|
@@ -1312,7 +1317,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;
|
|
@@ -1329,6 +1334,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;
|
|
@@ -1417,6 +1450,94 @@ static int si5341_clk_select_active_input(struct clk_si5341 *data)
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
+static ssize_t input_present_show(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct clk_si5341 *data = dev_get_drvdata(dev);
|
|
|
+ u32 status;
|
|
|
+ int res = regmap_read(data->regmap, SI5341_STATUS, &status);
|
|
|
+
|
|
|
+ if (res < 0)
|
|
|
+ return res;
|
|
|
+ res = !(status & SI5341_STATUS_LOSREF);
|
|
|
+ return snprintf(buf, PAGE_SIZE, "%d\n", res);
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(input_present);
|
|
|
+
|
|
|
+static ssize_t input_present_sticky_show(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct clk_si5341 *data = dev_get_drvdata(dev);
|
|
|
+ u32 status;
|
|
|
+ int res = regmap_read(data->regmap, SI5341_STATUS_STICKY, &status);
|
|
|
+
|
|
|
+ if (res < 0)
|
|
|
+ return res;
|
|
|
+ res = !(status & SI5341_STATUS_LOSREF);
|
|
|
+ return snprintf(buf, PAGE_SIZE, "%d\n", res);
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(input_present_sticky);
|
|
|
+
|
|
|
+static ssize_t pll_locked_show(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct clk_si5341 *data = dev_get_drvdata(dev);
|
|
|
+ u32 status;
|
|
|
+ int res = regmap_read(data->regmap, SI5341_STATUS, &status);
|
|
|
+
|
|
|
+ if (res < 0)
|
|
|
+ return res;
|
|
|
+ res = !(status & SI5341_STATUS_LOL);
|
|
|
+ return snprintf(buf, PAGE_SIZE, "%d\n", res);
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(pll_locked);
|
|
|
+
|
|
|
+static ssize_t pll_locked_sticky_show(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct clk_si5341 *data = dev_get_drvdata(dev);
|
|
|
+ u32 status;
|
|
|
+ int res = regmap_read(data->regmap, SI5341_STATUS_STICKY, &status);
|
|
|
+
|
|
|
+ if (res < 0)
|
|
|
+ return res;
|
|
|
+ res = !(status & SI5341_STATUS_LOL);
|
|
|
+ return snprintf(buf, PAGE_SIZE, "%d\n", res);
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(pll_locked_sticky);
|
|
|
+
|
|
|
+static ssize_t clear_sticky_store(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ struct clk_si5341 *data = dev_get_drvdata(dev);
|
|
|
+ long val;
|
|
|
+
|
|
|
+ if (kstrtol(buf, 10, &val))
|
|
|
+ return -EINVAL;
|
|
|
+ if (val) {
|
|
|
+ int res = regmap_write(data->regmap, SI5341_STATUS_STICKY, 0);
|
|
|
+
|
|
|
+ if (res < 0)
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+}
|
|
|
+static DEVICE_ATTR_WO(clear_sticky);
|
|
|
+
|
|
|
+static const struct attribute *si5341_attributes[] = {
|
|
|
+ &dev_attr_input_present.attr,
|
|
|
+ &dev_attr_input_present_sticky.attr,
|
|
|
+ &dev_attr_pll_locked.attr,
|
|
|
+ &dev_attr_pll_locked_sticky.attr,
|
|
|
+ &dev_attr_clear_sticky.attr,
|
|
|
+ NULL
|
|
|
+};
|
|
|
+
|
|
|
static int si5341_probe(struct i2c_client *client,
|
|
|
const struct i2c_device_id *id)
|
|
|
{
|
|
@@ -1424,7 +1545,7 @@ static int si5341_probe(struct i2c_client *client,
|
|
|
struct clk_init_data init;
|
|
|
struct clk *input;
|
|
|
const char *root_clock_name;
|
|
|
- const char *synth_clock_names[SI5341_NUM_SYNTH];
|
|
|
+ const char *synth_clock_names[SI5341_NUM_SYNTH] = { NULL };
|
|
|
int err;
|
|
|
unsigned int i;
|
|
|
struct clk_si5341_output_config config[SI5341_MAX_NUM_OUTPUTS];
|
|
@@ -1454,9 +1575,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))
|
|
@@ -1464,21 +1609,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;
|
|
|
}
|
|
@@ -1487,11 +1634,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
|
|
@@ -1504,19 +1651,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 */
|
|
@@ -1529,7 +1676,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;
|
|
@@ -1538,6 +1685,10 @@ static int si5341_probe(struct i2c_client *client,
|
|
|
for (i = 0; i < data->num_synth; ++i) {
|
|
|
synth_clock_names[i] = devm_kasprintf(&client->dev, GFP_KERNEL,
|
|
|
"%s.N%u", client->dev.of_node->name, i);
|
|
|
+ if (!synth_clock_names[i]) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto free_clk_names;
|
|
|
+ }
|
|
|
init.name = synth_clock_names[i];
|
|
|
data->synth[i].index = i;
|
|
|
data->synth[i].data = data;
|
|
@@ -1546,6 +1697,7 @@ static int si5341_probe(struct i2c_client *client,
|
|
|
if (err) {
|
|
|
dev_err(&client->dev,
|
|
|
"synth N%u registration failed\n", i);
|
|
|
+ goto free_clk_names;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1555,6 +1707,10 @@ static int si5341_probe(struct i2c_client *client,
|
|
|
for (i = 0; i < data->num_outputs; ++i) {
|
|
|
init.name = kasprintf(GFP_KERNEL, "%s.%d",
|
|
|
client->dev.of_node->name, i);
|
|
|
+ if (!init.name) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto free_clk_names;
|
|
|
+ }
|
|
|
init.flags = config[i].synth_master ? CLK_SET_RATE_PARENT : 0;
|
|
|
data->clk[i].index = i;
|
|
|
data->clk[i].data = data;
|
|
@@ -1566,13 +1722,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 free_clk_names;
|
|
|
}
|
|
|
if (config[i].always_on)
|
|
|
clk_prepare(data->clk[i].hw.clk);
|
|
@@ -1582,7 +1742,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 free_clk_names;
|
|
|
}
|
|
|
|
|
|
if (initialization_required) {
|
|
@@ -1590,11 +1750,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 free_clk_names;
|
|
|
|
|
|
err = si5341_finalize_defaults(data);
|
|
|
if (err < 0)
|
|
|
- return err;
|
|
|
+ goto free_clk_names;
|
|
|
}
|
|
|
|
|
|
/* wait for device to report input clock present and PLL lock */
|
|
@@ -1603,20 +1763,47 @@ 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 free_clk_names;
|
|
|
}
|
|
|
|
|
|
/* 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 free_clk_names;
|
|
|
}
|
|
|
|
|
|
+ err = sysfs_create_files(&client->dev.kobj, si5341_attributes);
|
|
|
+ if (err)
|
|
|
+ dev_err(&client->dev, "unable to create sysfs files\n");
|
|
|
+
|
|
|
+free_clk_names:
|
|
|
/* Free the names, clk framework makes copies */
|
|
|
for (i = 0; i < data->num_synth; ++i)
|
|
|
devm_kfree(&client->dev, (void *)synth_clock_names[i]);
|
|
|
|
|
|
+cleanup:
|
|
|
+ if (err) {
|
|
|
+ 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;
|
|
|
+
|
|
|
+ sysfs_remove_files(&client->dev.kobj, si5341_attributes);
|
|
|
+
|
|
|
+ for (i = 0; i < SI5341_MAX_NUM_OUTPUTS; ++i) {
|
|
|
+ if (data->clk[i].vddo_reg)
|
|
|
+ regulator_disable(data->clk[i].vddo_reg);
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1646,6 +1833,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);
|