aboutsummaryrefslogtreecommitdiff
path: root/drivers/clk
diff options
context:
space:
mode:
authorLiviu Dudau <Liviu.Dudau@arm.com>2014-02-20 12:56:24 +0000
committerJon Medhurst <tixy@linaro.org>2014-04-09 18:55:12 +0100
commitf325228ec61cefccc6197b8d05fb2c593d611251 (patch)
tree79b067fcbd1997bb66e93a949bccb4b581f628eb /drivers/clk
parentbab9b19bafe00a0022b9f2dd0a615cbb111aeca6 (diff)
clk: Add support for oscillators that can be controlled via the SCPI interface.
Signed-off-by: Liviu Dudau <Liviu.Dudau@arm.com>
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/Kconfig7
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-scpi.c155
3 files changed, 163 insertions, 0 deletions
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 7641965d208..1c2720d6813 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -45,6 +45,13 @@ config COMMON_CLK_MAX77686
---help---
This driver supports Maxim 77686 crystal oscillator clock.
+config COMMON_CLK_SCPI
+ tristate "Clock driver for oscillators controlled via SCPI interface"
+ depends on ARM_SCPI_MHU
+ ---help---
+ This driver provides support for oscillators that are controlled
+ by firmware that implements the SCPI interface.
+
config COMMON_CLK_SI5351
tristate "Clock driver for SiLabs 5351A/B/C"
depends on I2C
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index a367a983171..da36520b948 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o
obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o
obj-$(CONFIG_CLK_PPC_CORENET) += clk-ppc-corenet.o
obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
+obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o
diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c
new file mode 100644
index 00000000000..6ad981f66fb
--- /dev/null
+++ b/drivers/clk/clk-scpi.c
@@ -0,0 +1,155 @@
+/*
+ * Clock provider that uses the SCPI interface for clock setting.
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/kmod.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/scpi.h>
+
+struct scpi_osc {
+ uint8_t id;
+ struct clk_hw hw;
+ unsigned long rate_min;
+ unsigned long rate_max;
+ struct scpi_dev *scpi;
+};
+
+#define to_scpi_osc(osc) container_of(osc, struct scpi_osc, hw)
+
+static unsigned long scpi_osc_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct scpi_osc *osc = to_scpi_osc(hw);
+ char buf[4];
+ int err;
+
+ memset(buf, 0, 4);
+ buf[0] = osc->id;
+ err = scpi_exec_command(SCPI_CMD_GET_CLOCK_FREQ, &buf, 2, &buf, 4);
+ if (err)
+ return 0;
+
+ return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+}
+
+static long scpi_osc_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct scpi_osc *osc = to_scpi_osc(hw);
+ if (osc->rate_min && rate < osc->rate_min)
+ rate = osc->rate_min;
+ if (osc->rate_max && rate > osc->rate_max)
+ rate = osc->rate_max;
+
+ return rate;
+}
+
+static int scpi_osc_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct scpi_osc *osc = to_scpi_osc(hw);
+ char buf[6];
+ int err;
+
+ memcpy(buf, (char*)&rate, 4);
+ buf[4] = osc->id;
+ buf[5] = 0;
+ err = scpi_exec_command(SCPI_CMD_SET_CLOCK_FREQ, buf, 6, NULL, 0);
+ if (err) {
+ pr_info("Failed to set clock frequency: %d\n", err);
+ }
+
+ return err;
+}
+
+static struct clk_ops scpi_osc_ops = {
+ .recalc_rate = scpi_osc_recalc_rate,
+ .round_rate = scpi_osc_round_rate,
+ .set_rate = scpi_osc_set_rate,
+};
+
+void __init scpi_osc_setup(struct device_node *node)
+{
+ struct clk_init_data clk_init;
+ struct scpi_osc *osc;
+ struct clk *clk;
+ int num_parents, i, err;
+ const char *parent_names[2];
+ uint32_t range[2];
+ struct platform_device *pdev;
+ struct of_phandle_args clkspec;
+
+ osc = kzalloc(sizeof(*osc), GFP_KERNEL);
+ if (!osc)
+ return;
+
+ if (of_property_read_u32_array(node, "freq-range", range,
+ ARRAY_SIZE(range)) == 0) {
+ osc->rate_min = range[0];
+ osc->rate_max = range[1];
+ }
+
+ of_property_read_string(node, "clock-output-names", &clk_init.name);
+ if (!clk_init.name)
+ clk_init.name = node->full_name;
+
+ num_parents = of_clk_get_parent_count(node);
+ if (num_parents < 0 || num_parents > 2)
+ goto cleanup;
+
+ for (i = 0; i < num_parents; i++) {
+ parent_names[i] = of_clk_get_parent_name(node, i);
+ if (!parent_names[i])
+ goto cleanup;
+ else
+ pr_info("Found parent clock %s\n", parent_names[i]);
+
+ err = of_parse_phandle_with_args(node, "clocks",
+ "#clock-cells", i, &clkspec);
+ if (!err && clkspec.args_count)
+ osc->id = clkspec.args[0];
+ else
+ osc->id = -1;
+ }
+
+ request_module("scpi-mhu");
+
+ pdev = of_find_device_by_node(node);
+ if (!pdev) {
+ pr_info("Failed to find platform device\n");
+ } else {
+ pr_info("Found platform device %s\n", pdev->name);
+ }
+
+ clk_init.ops = &scpi_osc_ops;
+ clk_init.flags = CLK_IS_BASIC;
+ clk_init.num_parents = num_parents;
+ clk_init.parent_names = parent_names;
+ osc->hw.init = &clk_init;
+
+ clk = clk_register(NULL, &osc->hw);
+ if (IS_ERR(clk)) {
+ pr_err("Failed to register clock '%s'!\n", clk_init.name);
+ goto cleanup;
+ }
+
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ pr_info("Registered clock '%s'\n", clk_init.name);
+
+ return;
+
+cleanup:
+ if (osc)
+ kfree(osc);
+}
+CLK_OF_DECLARE(scpi_clk, "arm,scpi-osc", scpi_osc_setup);