clk: vc5: Add support for the input frequency doubler
authorMarek Vasut <marek.vasut@gmail.com>
Sun, 9 Jul 2017 13:28:12 +0000 (15:28 +0200)
committerStephen Boyd <sboyd@codeaurora.org>
Mon, 17 Jul 2017 18:50:59 +0000 (11:50 -0700)
The VersaClock 6 has an input frequency doubler between the input
clock mux and the predivider. Add new capability flag and support
for this frequency doubler block into the driver.

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Stephen Boyd <sboyd@codeaurora.org>
Cc: Alexey Firago <alexey_firago@mentor.com>
Cc: Michael Turquette <mturquette@baylibre.com>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: linux-renesas-soc@vger.kernel.org
Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
on Salvator-XS with the display LVDS output.
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
drivers/clk/clk-versaclock5.c

index cae07267a884f5233582382fc2d0499e1cca357b..2bdd456f89e4c885af79ac65b4654e8c9d06d022 100644 (file)
@@ -57,6 +57,7 @@
 #define VC5_PRIM_SRC_SHDN                      0x10
 #define VC5_PRIM_SRC_SHDN_EN_XTAL              BIT(7)
 #define VC5_PRIM_SRC_SHDN_EN_CLKIN             BIT(6)
+#define VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ  BIT(3)
 #define VC5_PRIM_SRC_SHDN_SP                   BIT(1)
 #define VC5_PRIM_SRC_SHDN_EN_GBL_SHDN          BIT(0)
 
 /* flags to describe chip features */
 /* chip has built-in oscilator */
 #define VC5_HAS_INTERNAL_XTAL  BIT(0)
+/* chip has PFD requency doubler */
+#define VC5_HAS_PFD_FREQ_DBL   BIT(1)
 
 /* Supported IDT VC5 models. */
 enum vc5_model {
@@ -157,6 +160,7 @@ struct vc5_driver_data {
        struct clk              *pin_clkin;
        unsigned char           clk_mux_ins;
        struct clk_hw           clk_mux;
+       struct clk_hw           clk_mul;
        struct clk_hw           clk_pfd;
        struct vc5_hw_data      clk_pll;
        struct vc5_hw_data      clk_fod[VC5_MAX_FOD_NUM];
@@ -167,6 +171,10 @@ static const char * const vc5_mux_names[] = {
        "mux"
 };
 
+static const char * const vc5_dbl_names[] = {
+       "dbl"
+};
+
 static const char * const vc5_pfd_names[] = {
        "pfd"
 };
@@ -264,6 +272,54 @@ static const struct clk_ops vc5_mux_ops = {
        .get_parent     = vc5_mux_get_parent,
 };
 
+static unsigned long vc5_dbl_recalc_rate(struct clk_hw *hw,
+                                        unsigned long parent_rate)
+{
+       struct vc5_driver_data *vc5 =
+               container_of(hw, struct vc5_driver_data, clk_mul);
+       unsigned int premul;
+
+       regmap_read(vc5->regmap, VC5_PRIM_SRC_SHDN, &premul);
+       if (premul & VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ)
+               parent_rate *= 2;
+
+       return parent_rate;
+}
+
+static long vc5_dbl_round_rate(struct clk_hw *hw, unsigned long rate,
+                              unsigned long *parent_rate)
+{
+       if ((*parent_rate == rate) || ((*parent_rate * 2) == rate))
+               return rate;
+       else
+               return -EINVAL;
+}
+
+static int vc5_dbl_set_rate(struct clk_hw *hw, unsigned long rate,
+                           unsigned long parent_rate)
+{
+       struct vc5_driver_data *vc5 =
+               container_of(hw, struct vc5_driver_data, clk_mul);
+       u32 mask;
+
+       if ((parent_rate * 2) == rate)
+               mask = VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ;
+       else
+               mask = 0;
+
+       regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN,
+                          VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ,
+                          mask);
+
+       return 0;
+}
+
+static const struct clk_ops vc5_dbl_ops = {
+       .recalc_rate    = vc5_dbl_recalc_rate,
+       .round_rate     = vc5_dbl_round_rate,
+       .set_rate       = vc5_dbl_set_rate,
+};
+
 static unsigned long vc5_pfd_recalc_rate(struct clk_hw *hw,
                                         unsigned long parent_rate)
 {
@@ -706,12 +762,32 @@ static int vc5_probe(struct i2c_client *client,
                goto err_clk;
        }
 
+       if (vc5->chip_info->flags & VC5_HAS_PFD_FREQ_DBL) {
+               /* Register frequency doubler */
+               memset(&init, 0, sizeof(init));
+               init.name = vc5_dbl_names[0];
+               init.ops = &vc5_dbl_ops;
+               init.flags = CLK_SET_RATE_PARENT;
+               init.parent_names = vc5_mux_names;
+               init.num_parents = 1;
+               vc5->clk_mul.init = &init;
+               ret = devm_clk_hw_register(&client->dev, &vc5->clk_mul);
+               if (ret) {
+                       dev_err(&client->dev, "unable to register %s\n",
+                               init.name);
+                       goto err_clk;
+               }
+       }
+
        /* Register PFD */
        memset(&init, 0, sizeof(init));
        init.name = vc5_pfd_names[0];
        init.ops = &vc5_pfd_ops;
        init.flags = CLK_SET_RATE_PARENT;
-       init.parent_names = vc5_mux_names;
+       if (vc5->chip_info->flags & VC5_HAS_PFD_FREQ_DBL)
+               init.parent_names = vc5_dbl_names;
+       else
+               init.parent_names = vc5_mux_names;
        init.num_parents = 1;
        vc5->clk_pfd.init = &init;
        ret = devm_clk_hw_register(&client->dev, &vc5->clk_pfd);