Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / clk / clk-wm831x.c
diff --git a/kernel/drivers/clk/clk-wm831x.c b/kernel/drivers/clk/clk-wm831x.c
new file mode 100644 (file)
index 0000000..ef67719
--- /dev/null
@@ -0,0 +1,407 @@
+/*
+ * WM831x clock control
+ *
+ * Copyright 2011-2 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/wm831x/core.h>
+
+struct wm831x_clk {
+       struct wm831x *wm831x;
+       struct clk_hw xtal_hw;
+       struct clk_hw fll_hw;
+       struct clk_hw clkout_hw;
+       struct clk *xtal;
+       struct clk *fll;
+       struct clk *clkout;
+       bool xtal_ena;
+};
+
+static int wm831x_xtal_is_prepared(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 xtal_hw);
+
+       return clkdata->xtal_ena;
+}
+
+static unsigned long wm831x_xtal_recalc_rate(struct clk_hw *hw,
+                                            unsigned long parent_rate)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 xtal_hw);
+
+       if (clkdata->xtal_ena)
+               return 32768;
+       else
+               return 0;
+}
+
+static const struct clk_ops wm831x_xtal_ops = {
+       .is_prepared = wm831x_xtal_is_prepared,
+       .recalc_rate = wm831x_xtal_recalc_rate,
+};
+
+static struct clk_init_data wm831x_xtal_init = {
+       .name = "xtal",
+       .ops = &wm831x_xtal_ops,
+       .flags = CLK_IS_ROOT,
+};
+
+static const unsigned long wm831x_fll_auto_rates[] = {
+        2048000,
+       11289600,
+       12000000,
+       12288000,
+       19200000,
+       22579600,
+       24000000,
+       24576000,
+};
+
+static int wm831x_fll_is_prepared(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 fll_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_reg_read(wm831x, WM831X_FLL_CONTROL_1);
+       if (ret < 0) {
+               dev_err(wm831x->dev, "Unable to read FLL_CONTROL_1: %d\n",
+                       ret);
+               return true;
+       }
+
+       return (ret & WM831X_FLL_ENA) != 0;
+}
+
+static int wm831x_fll_prepare(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 fll_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_set_bits(wm831x, WM831X_FLL_CONTROL_1,
+                             WM831X_FLL_ENA, WM831X_FLL_ENA);
+       if (ret != 0)
+               dev_crit(wm831x->dev, "Failed to enable FLL: %d\n", ret);
+
+       usleep_range(2000, 2000);
+
+       return ret;
+}
+
+static void wm831x_fll_unprepare(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 fll_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_set_bits(wm831x, WM831X_FLL_CONTROL_1, WM831X_FLL_ENA, 0);
+       if (ret != 0)
+               dev_crit(wm831x->dev, "Failed to disable FLL: %d\n", ret);
+}
+
+static unsigned long wm831x_fll_recalc_rate(struct clk_hw *hw,
+                                           unsigned long parent_rate)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 fll_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_reg_read(wm831x, WM831X_CLOCK_CONTROL_2);
+       if (ret < 0) {
+               dev_err(wm831x->dev, "Unable to read CLOCK_CONTROL_2: %d\n",
+                       ret);
+               return 0;
+       }
+
+       if (ret & WM831X_FLL_AUTO)
+               return wm831x_fll_auto_rates[ret & WM831X_FLL_AUTO_FREQ_MASK];
+
+       dev_err(wm831x->dev, "FLL only supported in AUTO mode\n");
+
+       return 0;
+}
+
+static long wm831x_fll_round_rate(struct clk_hw *hw, unsigned long rate,
+                                 unsigned long *unused)
+{
+       int best = 0;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(wm831x_fll_auto_rates); i++)
+               if (abs(wm831x_fll_auto_rates[i] - rate) <
+                   abs(wm831x_fll_auto_rates[best] - rate))
+                       best = i;
+
+       return wm831x_fll_auto_rates[best];
+}
+
+static int wm831x_fll_set_rate(struct clk_hw *hw, unsigned long rate,
+                              unsigned long parent_rate)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 fll_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(wm831x_fll_auto_rates); i++)
+               if (wm831x_fll_auto_rates[i] == rate)
+                       break;
+       if (i == ARRAY_SIZE(wm831x_fll_auto_rates))
+               return -EINVAL;
+
+       if (wm831x_fll_is_prepared(hw))
+               return -EPERM;
+
+       return wm831x_set_bits(wm831x, WM831X_CLOCK_CONTROL_2,
+                              WM831X_FLL_AUTO_FREQ_MASK, i);
+}
+
+static const char *wm831x_fll_parents[] = {
+       "xtal",
+       "clkin",
+};
+
+static u8 wm831x_fll_get_parent(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 fll_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       /* AUTO mode is always clocked from the crystal */
+       ret = wm831x_reg_read(wm831x, WM831X_CLOCK_CONTROL_2);
+       if (ret < 0) {
+               dev_err(wm831x->dev, "Unable to read CLOCK_CONTROL_2: %d\n",
+                       ret);
+               return 0;
+       }
+
+       if (ret & WM831X_FLL_AUTO)
+               return 0;
+
+       ret = wm831x_reg_read(wm831x, WM831X_FLL_CONTROL_5);
+       if (ret < 0) {
+               dev_err(wm831x->dev, "Unable to read FLL_CONTROL_5: %d\n",
+                       ret);
+               return 0;
+       }
+
+       switch (ret & WM831X_FLL_CLK_SRC_MASK) {
+       case 0:
+               return 0;
+       case 1:
+               return 1;
+       default:
+               dev_err(wm831x->dev, "Unsupported FLL clock source %d\n",
+                       ret & WM831X_FLL_CLK_SRC_MASK);
+               return 0;
+       }
+}
+
+static const struct clk_ops wm831x_fll_ops = {
+       .is_prepared = wm831x_fll_is_prepared,
+       .prepare = wm831x_fll_prepare,
+       .unprepare = wm831x_fll_unprepare,
+       .round_rate = wm831x_fll_round_rate,
+       .recalc_rate = wm831x_fll_recalc_rate,
+       .set_rate = wm831x_fll_set_rate,
+       .get_parent = wm831x_fll_get_parent,
+};
+
+static struct clk_init_data wm831x_fll_init = {
+       .name = "fll",
+       .ops = &wm831x_fll_ops,
+       .parent_names = wm831x_fll_parents,
+       .num_parents = ARRAY_SIZE(wm831x_fll_parents),
+       .flags = CLK_SET_RATE_GATE,
+};
+
+static int wm831x_clkout_is_prepared(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 clkout_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_reg_read(wm831x, WM831X_CLOCK_CONTROL_1);
+       if (ret < 0) {
+               dev_err(wm831x->dev, "Unable to read CLOCK_CONTROL_1: %d\n",
+                       ret);
+               return true;
+       }
+
+       return (ret & WM831X_CLKOUT_ENA) != 0;
+}
+
+static int wm831x_clkout_prepare(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 clkout_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_reg_unlock(wm831x);
+       if (ret != 0) {
+               dev_crit(wm831x->dev, "Failed to lock registers: %d\n", ret);
+               return ret;
+       }
+
+       ret = wm831x_set_bits(wm831x, WM831X_CLOCK_CONTROL_1,
+                             WM831X_CLKOUT_ENA, WM831X_CLKOUT_ENA);
+       if (ret != 0)
+               dev_crit(wm831x->dev, "Failed to enable CLKOUT: %d\n", ret);
+
+       wm831x_reg_lock(wm831x);
+
+       return ret;
+}
+
+static void wm831x_clkout_unprepare(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 clkout_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_reg_unlock(wm831x);
+       if (ret != 0) {
+               dev_crit(wm831x->dev, "Failed to lock registers: %d\n", ret);
+               return;
+       }
+
+       ret = wm831x_set_bits(wm831x, WM831X_CLOCK_CONTROL_1,
+                             WM831X_CLKOUT_ENA, 0);
+       if (ret != 0)
+               dev_crit(wm831x->dev, "Failed to disable CLKOUT: %d\n", ret);
+
+       wm831x_reg_lock(wm831x);
+}
+
+static const char *wm831x_clkout_parents[] = {
+       "fll",
+       "xtal",
+};
+
+static u8 wm831x_clkout_get_parent(struct clk_hw *hw)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 clkout_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+       int ret;
+
+       ret = wm831x_reg_read(wm831x, WM831X_CLOCK_CONTROL_1);
+       if (ret < 0) {
+               dev_err(wm831x->dev, "Unable to read CLOCK_CONTROL_1: %d\n",
+                       ret);
+               return 0;
+       }
+
+       if (ret & WM831X_CLKOUT_SRC)
+               return 1;
+       else
+               return 0;
+}
+
+static int wm831x_clkout_set_parent(struct clk_hw *hw, u8 parent)
+{
+       struct wm831x_clk *clkdata = container_of(hw, struct wm831x_clk,
+                                                 clkout_hw);
+       struct wm831x *wm831x = clkdata->wm831x;
+
+       return wm831x_set_bits(wm831x, WM831X_CLOCK_CONTROL_1,
+                              WM831X_CLKOUT_SRC,
+                              parent << WM831X_CLKOUT_SRC_SHIFT);
+}
+
+static const struct clk_ops wm831x_clkout_ops = {
+       .is_prepared = wm831x_clkout_is_prepared,
+       .prepare = wm831x_clkout_prepare,
+       .unprepare = wm831x_clkout_unprepare,
+       .get_parent = wm831x_clkout_get_parent,
+       .set_parent = wm831x_clkout_set_parent,
+};
+
+static struct clk_init_data wm831x_clkout_init = {
+       .name = "clkout",
+       .ops = &wm831x_clkout_ops,
+       .parent_names = wm831x_clkout_parents,
+       .num_parents = ARRAY_SIZE(wm831x_clkout_parents),
+       .flags = CLK_SET_RATE_PARENT,
+};
+
+static int wm831x_clk_probe(struct platform_device *pdev)
+{
+       struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+       struct wm831x_clk *clkdata;
+       int ret;
+
+       clkdata = devm_kzalloc(&pdev->dev, sizeof(*clkdata), GFP_KERNEL);
+       if (!clkdata)
+               return -ENOMEM;
+
+       clkdata->wm831x = wm831x;
+
+       /* XTAL_ENA can only be set via OTP/InstantConfig so just read once */
+       ret = wm831x_reg_read(wm831x, WM831X_CLOCK_CONTROL_2);
+       if (ret < 0) {
+               dev_err(wm831x->dev, "Unable to read CLOCK_CONTROL_2: %d\n",
+                       ret);
+               return ret;
+       }
+       clkdata->xtal_ena = ret & WM831X_XTAL_ENA;
+
+       clkdata->xtal_hw.init = &wm831x_xtal_init;
+       clkdata->xtal = devm_clk_register(&pdev->dev, &clkdata->xtal_hw);
+       if (IS_ERR(clkdata->xtal))
+               return PTR_ERR(clkdata->xtal);
+
+       clkdata->fll_hw.init = &wm831x_fll_init;
+       clkdata->fll = devm_clk_register(&pdev->dev, &clkdata->fll_hw);
+       if (IS_ERR(clkdata->fll))
+               return PTR_ERR(clkdata->fll);
+
+       clkdata->clkout_hw.init = &wm831x_clkout_init;
+       clkdata->clkout = devm_clk_register(&pdev->dev, &clkdata->clkout_hw);
+       if (IS_ERR(clkdata->clkout))
+               return PTR_ERR(clkdata->clkout);
+
+       platform_set_drvdata(pdev, clkdata);
+
+       return 0;
+}
+
+static struct platform_driver wm831x_clk_driver = {
+       .probe = wm831x_clk_probe,
+       .driver         = {
+               .name   = "wm831x-clk",
+       },
+};
+
+module_platform_driver(wm831x_clk_driver);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM831x clock driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-clk");