clk, clk-si5341: Support multiple input ports
authorMike Looijmans <mike.looijmans@topic.nl>
Tue, 7 Jan 2020 07:53:40 +0000 (08:53 +0100)
committerStephen Boyd <sboyd@kernel.org>
Tue, 28 Jan 2020 21:05:54 +0000 (13:05 -0800)
The Si5341 and Si5340 have multiple input clock options. So far, the driver
only supported the XTAL input, this adds support for the three external
clock inputs as well.

If the clock chip isn't programmed at boot, the driver will default to the
XTAL input as before. If there is no "xtal" clock input available, it will
pick the first connected input (e.g. "in0") as the input clock for the PLL.
One can use clock-assigned-parents to select a particular clock as input.

Signed-off-by: Mike Looijmans <mike.looijmans@topic.nl>
Link: https://lkml.kernel.org/r/20200107075340.14528-1-mike.looijmans@topic.nl
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
drivers/clk/clk-si5341.c

index 6e780c2a9e6ba4290cf8bce60936c5a943d4e1e4..3c228b0181161ff0c2a811fd4609cf37c8ff7bca 100644 (file)
@@ -16,6 +16,8 @@
 #include <linux/slab.h>
 #include <asm/unaligned.h>
 
+#define SI5341_NUM_INPUTS 4
+
 #define SI5341_MAX_NUM_OUTPUTS 10
 #define SI5340_MAX_NUM_OUTPUTS 4
 
@@ -56,8 +58,8 @@ struct clk_si5341 {
        struct i2c_client *i2c_client;
        struct clk_si5341_synth synth[SI5341_NUM_SYNTH];
        struct clk_si5341_output clk[SI5341_MAX_NUM_OUTPUTS];
-       struct clk *pxtal;
-       const char *pxtal_name;
+       struct clk *input_clk[SI5341_NUM_INPUTS];
+       const char *input_clk_name[SI5341_NUM_INPUTS];
        const u16 *reg_output_offset;
        const u16 *reg_rdiv_offset;
        u64 freq_vco; /* 13500–14256 MHz */
@@ -78,10 +80,25 @@ struct clk_si5341_output_config {
 #define SI5341_DEVICE_REV      0x0005
 #define SI5341_STATUS          0x000C
 #define SI5341_SOFT_RST                0x001C
+#define SI5341_IN_SEL          0x0021
+#define SI5341_XAXB_CFG                0x090E
+#define SI5341_IN_EN           0x0949
+#define SI5341_INX_TO_PFD_EN   0x094A
+
+/* Input selection */
+#define SI5341_IN_SEL_MASK     0x06
+#define SI5341_IN_SEL_SHIFT    1
+#define SI5341_IN_SEL_REGCTRL  0x01
+#define SI5341_INX_TO_PFD_SHIFT        4
+
+/* XTAL config bits */
+#define SI5341_XAXB_CFG_EXTCLK_EN      BIT(0)
+#define SI5341_XAXB_CFG_PDNB           BIT(1)
 
 /* Input dividers (48-bit) */
 #define SI5341_IN_PDIV(x)      (0x0208 + ((x) * 10))
 #define SI5341_IN_PSET(x)      (0x020E + ((x) * 10))
+#define SI5341_PX_UPD          0x0230
 
 /* PLL configuration */
 #define SI5341_PLL_M_NUM       0x0235
@@ -120,6 +137,10 @@ struct si5341_reg_default {
        u8 value;
 };
 
+static const char * const si5341_input_clock_names[] = {
+       "in0", "in1", "in2", "xtal"
+};
+
 /* Output configuration registers 0..9 are not quite logically organized */
 static const u16 si5341_reg_output_offset[] = {
        0x0108,
@@ -390,7 +411,112 @@ static unsigned long si5341_clk_recalc_rate(struct clk_hw *hw,
        return (unsigned long)res;
 }
 
+static int si5341_clk_get_selected_input(struct clk_si5341 *data)
+{
+       int err;
+       u32 val;
+
+       err = regmap_read(data->regmap, SI5341_IN_SEL, &val);
+       if (err < 0)
+               return err;
+
+       return (val & SI5341_IN_SEL_MASK) >> SI5341_IN_SEL_SHIFT;
+}
+
+static u8 si5341_clk_get_parent(struct clk_hw *hw)
+{
+       struct clk_si5341 *data = to_clk_si5341(hw);
+       int res = si5341_clk_get_selected_input(data);
+
+       if (res < 0)
+               return 0; /* Apparently we cannot report errors */
+
+       return res;
+}
+
+static int si5341_clk_reparent(struct clk_si5341 *data, u8 index)
+{
+       int err;
+       u8 val;
+
+       val = (index << SI5341_IN_SEL_SHIFT) & SI5341_IN_SEL_MASK;
+       /* Enable register-based input selection */
+       val |= SI5341_IN_SEL_REGCTRL;
+
+       err = regmap_update_bits(data->regmap,
+               SI5341_IN_SEL, SI5341_IN_SEL_REGCTRL | SI5341_IN_SEL_MASK, val);
+       if (err < 0)
+               return err;
+
+       if (index < 3) {
+               /* Enable input buffer for selected input */
+               err = regmap_update_bits(data->regmap,
+                               SI5341_IN_EN, 0x07, BIT(index));
+               if (err < 0)
+                       return err;
+
+               /* Enables the input to phase detector */
+               err = regmap_update_bits(data->regmap, SI5341_INX_TO_PFD_EN,
+                               0x7 << SI5341_INX_TO_PFD_SHIFT,
+                               BIT(index + SI5341_INX_TO_PFD_SHIFT));
+               if (err < 0)
+                       return err;
+
+               /* Power down XTAL oscillator and buffer */
+               err = regmap_update_bits(data->regmap, SI5341_XAXB_CFG,
+                               SI5341_XAXB_CFG_PDNB, 0);
+               if (err < 0)
+                       return err;
+
+               /*
+                * Set the P divider to "1". There's no explanation in the
+                * datasheet of these registers, but the clockbuilder software
+                * programs a "1" when the input is being used.
+                */
+               err = regmap_write(data->regmap, SI5341_IN_PDIV(index), 1);
+               if (err < 0)
+                       return err;
+
+               err = regmap_write(data->regmap, SI5341_IN_PSET(index), 1);
+               if (err < 0)
+                       return err;
+
+               /* Set update PDIV bit */
+               err = regmap_write(data->regmap, SI5341_PX_UPD, BIT(index));
+               if (err < 0)
+                       return err;
+       } else {
+               /* Disable all input buffers */
+               err = regmap_update_bits(data->regmap, SI5341_IN_EN, 0x07, 0);
+               if (err < 0)
+                       return err;
+
+               /* Disable input to phase detector */
+               err = regmap_update_bits(data->regmap, SI5341_INX_TO_PFD_EN,
+                               0x7 << SI5341_INX_TO_PFD_SHIFT, 0);
+               if (err < 0)
+                       return err;
+
+               /* Power up XTAL oscillator and buffer */
+               err = regmap_update_bits(data->regmap, SI5341_XAXB_CFG,
+                               SI5341_XAXB_CFG_PDNB, SI5341_XAXB_CFG_PDNB);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int si5341_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+       struct clk_si5341 *data = to_clk_si5341(hw);
+
+       return si5341_clk_reparent(data, index);
+}
+
 static const struct clk_ops si5341_clk_ops = {
+       .set_parent = si5341_clk_set_parent,
+       .get_parent = si5341_clk_get_parent,
        .recalc_rate = si5341_clk_recalc_rate,
 };
 
@@ -985,7 +1111,8 @@ static const struct regmap_range si5341_regmap_volatile_range[] = {
        regmap_reg_range(0x000C, 0x0012), /* Status */
        regmap_reg_range(0x001C, 0x001E), /* reset, finc/fdec */
        regmap_reg_range(0x00E2, 0x00FE), /* NVM, interrupts, device ready */
-       /* Update bits for synth config */
+       /* Update bits for P divider and synth config */
+       regmap_reg_range(SI5341_PX_UPD, SI5341_PX_UPD),
        regmap_reg_range(SI5341_SYNTH_N_UPD(0), SI5341_SYNTH_N_UPD(0)),
        regmap_reg_range(SI5341_SYNTH_N_UPD(1), SI5341_SYNTH_N_UPD(1)),
        regmap_reg_range(SI5341_SYNTH_N_UPD(2), SI5341_SYNTH_N_UPD(2)),
@@ -1122,6 +1249,7 @@ static int si5341_initialize_pll(struct clk_si5341 *data)
        struct device_node *np = data->i2c_client->dev.of_node;
        u32 m_num = 0;
        u32 m_den = 0;
+       int sel;
 
        if (of_property_read_u32(np, "silabs,pll-m-num", &m_num)) {
                dev_err(&data->i2c_client->dev,
@@ -1135,7 +1263,11 @@ static int si5341_initialize_pll(struct clk_si5341 *data)
        if (!m_num || !m_den) {
                dev_err(&data->i2c_client->dev,
                        "PLL configuration invalid, assume 14GHz\n");
-               m_den = clk_get_rate(data->pxtal) / 10;
+               sel = si5341_clk_get_selected_input(data);
+               if (sel < 0)
+                       return sel;
+
+               m_den = clk_get_rate(data->input_clk[sel]) / 10;
                m_num = 1400000000;
        }
 
@@ -1143,11 +1275,52 @@ static int si5341_initialize_pll(struct clk_si5341 *data)
                        SI5341_PLL_M_NUM, m_num, m_den);
 }
 
+static int si5341_clk_select_active_input(struct clk_si5341 *data)
+{
+       int res;
+       int err;
+       int i;
+
+       res = si5341_clk_get_selected_input(data);
+       if (res < 0)
+               return res;
+
+       /* If the current register setting is invalid, pick the first input */
+       if (!data->input_clk[res]) {
+               dev_dbg(&data->i2c_client->dev,
+                       "Input %d not connected, rerouting\n", res);
+               res = -ENODEV;
+               for (i = 0; i < SI5341_NUM_INPUTS; ++i) {
+                       if (data->input_clk[i]) {
+                               res = i;
+                               break;
+                       }
+               }
+               if (res < 0) {
+                       dev_err(&data->i2c_client->dev,
+                               "No clock input available\n");
+                       return res;
+               }
+       }
+
+       /* Make sure the selected clock is also enabled and routed */
+       err = si5341_clk_reparent(data, res);
+       if (err < 0)
+               return err;
+
+       err = clk_prepare_enable(data->input_clk[res]);
+       if (err < 0)
+               return err;
+
+       return res;
+}
+
 static int si5341_probe(struct i2c_client *client,
                const struct i2c_device_id *id)
 {
        struct clk_si5341 *data;
        struct clk_init_data init;
+       struct clk *input;
        const char *root_clock_name;
        const char *synth_clock_names[SI5341_NUM_SYNTH];
        int err;
@@ -1161,12 +1334,16 @@ static int si5341_probe(struct i2c_client *client,
 
        data->i2c_client = client;
 
-       data->pxtal = devm_clk_get(&client->dev, "xtal");
-       if (IS_ERR(data->pxtal)) {
-               if (PTR_ERR(data->pxtal) == -EPROBE_DEFER)
-                       return -EPROBE_DEFER;
-
-               dev_err(&client->dev, "Missing xtal clock input\n");
+       for (i = 0; i < SI5341_NUM_INPUTS; ++i) {
+               input = devm_clk_get(&client->dev, si5341_input_clock_names[i]);
+               if (IS_ERR(input)) {
+                       if (PTR_ERR(input) == -EPROBE_DEFER)
+                               return -EPROBE_DEFER;
+                       data->input_clk_name[i] = si5341_input_clock_names[i];
+               } else {
+                       data->input_clk[i] = input;
+                       data->input_clk_name[i] = __clk_get_name(input);
+               }
        }
 
        err = si5341_dt_parse_dt(client, config);
@@ -1188,9 +1365,6 @@ static int si5341_probe(struct i2c_client *client,
        if (err < 0)
                return err;
 
-       /* "Activate" the xtal (usually a fixed clock) */
-       clk_prepare_enable(data->pxtal);
-
        if (of_property_read_bool(client->dev.of_node, "silabs,reprogram")) {
                initialization_required = true;
        } else {
@@ -1223,7 +1397,14 @@ static int si5341_probe(struct i2c_client *client,
                                        ARRAY_SIZE(si5341_reg_defaults));
                if (err < 0)
                        return err;
+       }
+
+       /* Input must be up and running at this point */
+       err = si5341_clk_select_active_input(data);
+       if (err < 0)
+               return err;
 
+       if (initialization_required) {
                /* PLL configuration is required */
                err = si5341_initialize_pll(data);
                if (err < 0)
@@ -1231,9 +1412,8 @@ static int si5341_probe(struct i2c_client *client,
        }
 
        /* Register the PLL */
-       data->pxtal_name = __clk_get_name(data->pxtal);
-       init.parent_names = &data->pxtal_name;
-       init.num_parents = 1; /* For now, only XTAL input supported */
+       init.parent_names = data->input_clk_name;
+       init.num_parents = SI5341_NUM_INPUTS;
        init.ops = &si5341_clk_ops;
        init.flags = 0;
        data->hw.init = &init;