diff options
author | Jon Medhurst <tixy@linaro.org> | 2014-04-15 11:51:19 +0100 |
---|---|---|
committer | Jon Medhurst <tixy@linaro.org> | 2014-04-15 11:51:19 +0100 |
commit | 9b3bf8cffaec58256681bad7682ec201fd41f3d3 (patch) | |
tree | 11cc064b6de7023a857dcb3fd8be2b29f9c34f74 /drivers/clk | |
parent | cd3428916381f1b160cd062827092337e80d5a08 (diff) | |
parent | 5d492c0fc489ac6d0a95a8ed092ec86a0b4bdd16 (diff) |
Merge branch 'tracking-armlt-juno' into integration-linaro-vexpress
Conflicts:
arch/arm64/kernel/debug-monitors.c
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/Kconfig | 7 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk-scpi.c | 155 |
3 files changed, 163 insertions, 0 deletions
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index f9f605695e4..f7caa2d33f0 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 88af4a399d6..3baa0da3854 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); |