diff options
-rw-r--r-- | Documentation/clk.txt | 4 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/clock/axi-clkgen.txt | 22 | ||||
-rw-r--r-- | drivers/clk/Kconfig | 8 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk-axi-clkgen.c | 331 | ||||
-rw-r--r-- | drivers/clk/clk.c | 52 | ||||
-rw-r--r-- | drivers/clk/mxs/clk.c | 1 | ||||
-rw-r--r-- | drivers/clk/spear/spear1340_clock.c | 18 | ||||
-rw-r--r-- | drivers/clk/ux500/clk-prcmu.c | 134 | ||||
-rw-r--r-- | include/linux/clk-provider.h | 11 |
10 files changed, 517 insertions, 65 deletions
diff --git a/Documentation/clk.txt b/Documentation/clk.txt index 1943fae014fd..4274a546eb57 100644 --- a/Documentation/clk.txt +++ b/Documentation/clk.txt @@ -174,9 +174,9 @@ int clk_foo_enable(struct clk_hw *hw) }; Below is a matrix detailing which clk_ops are mandatory based upon the -hardware capbilities of that clock. A cell marked as "y" means +hardware capabilities of that clock. A cell marked as "y" means mandatory, a cell marked as "n" implies that either including that -callback is invalid or otherwise uneccesary. Empty cells are either +callback is invalid or otherwise unnecessary. Empty cells are either optional or must be evaluated on a case-by-case basis. clock hardware characteristics diff --git a/Documentation/devicetree/bindings/clock/axi-clkgen.txt b/Documentation/devicetree/bindings/clock/axi-clkgen.txt new file mode 100644 index 000000000000..028b493e97ff --- /dev/null +++ b/Documentation/devicetree/bindings/clock/axi-clkgen.txt @@ -0,0 +1,22 @@ +Binding for the axi-clkgen clock generator + +This binding uses the common clock binding[1]. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt + +Required properties: +- compatible : shall be "adi,axi-clkgen". +- #clock-cells : from common clock binding; Should always be set to 0. +- reg : Address and length of the axi-clkgen register set. +- clocks : Phandle and clock specifier for the parent clock. + +Optional properties: +- clock-output-names : From common clock binding. + +Example: + clock@0xff000000 { + compatible = "adi,axi-clkgen"; + #clock-cells = <0>; + reg = <0xff000000 0x1000>; + clocks = <&osc 1>; + }; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index a47e6ee98b8c..a64caefdba12 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -63,6 +63,14 @@ config CLK_TWL6040 McPDM. McPDM module is using the external bit clock on the McPDM bus as functional clock. +config COMMON_CLK_AXI_CLKGEN + tristate "AXI clkgen driver" + depends on ARCH_ZYNQ || MICROBLAZE + help + ---help--- + Support for the Analog Devices axi-clkgen pcore clock generator for Xilinx + FPGAs. It is commonly used in Analog Devices' reference designs. + endmenu source "drivers/clk/mvebu/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 0147022b9813..518d48724084 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_PLAT_SAMSUNG) += samsung/ obj-$(CONFIG_X86) += x86/ # Chip specific +obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o diff --git a/drivers/clk/clk-axi-clkgen.c b/drivers/clk/clk-axi-clkgen.c new file mode 100644 index 000000000000..8137327847c3 --- /dev/null +++ b/drivers/clk/clk-axi-clkgen.c @@ -0,0 +1,331 @@ +/* + * AXI clkgen driver + * + * Copyright 2012-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + * + */ + +#include <linux/platform_device.h> +#include <linux/clk-provider.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/err.h> + +#define AXI_CLKGEN_REG_UPDATE_ENABLE 0x04 +#define AXI_CLKGEN_REG_CLK_OUT1 0x08 +#define AXI_CLKGEN_REG_CLK_OUT2 0x0c +#define AXI_CLKGEN_REG_CLK_DIV 0x10 +#define AXI_CLKGEN_REG_CLK_FB1 0x14 +#define AXI_CLKGEN_REG_CLK_FB2 0x18 +#define AXI_CLKGEN_REG_LOCK1 0x1c +#define AXI_CLKGEN_REG_LOCK2 0x20 +#define AXI_CLKGEN_REG_LOCK3 0x24 +#define AXI_CLKGEN_REG_FILTER1 0x28 +#define AXI_CLKGEN_REG_FILTER2 0x2c + +struct axi_clkgen { + void __iomem *base; + struct clk_hw clk_hw; +}; + +static uint32_t axi_clkgen_lookup_filter(unsigned int m) +{ + switch (m) { + case 0: + return 0x01001990; + case 1: + return 0x01001190; + case 2: + return 0x01009890; + case 3: + return 0x01001890; + case 4: + return 0x01008890; + case 5 ... 8: + return 0x01009090; + case 9 ... 11: + return 0x01000890; + case 12: + return 0x08009090; + case 13 ... 22: + return 0x01001090; + case 23 ... 36: + return 0x01008090; + case 37 ... 46: + return 0x08001090; + default: + return 0x08008090; + } +} + +static const uint32_t axi_clkgen_lock_table[] = { + 0x060603e8, 0x060603e8, 0x080803e8, 0x0b0b03e8, + 0x0e0e03e8, 0x111103e8, 0x131303e8, 0x161603e8, + 0x191903e8, 0x1c1c03e8, 0x1f1f0384, 0x1f1f0339, + 0x1f1f02ee, 0x1f1f02bc, 0x1f1f028a, 0x1f1f0271, + 0x1f1f023f, 0x1f1f0226, 0x1f1f020d, 0x1f1f01f4, + 0x1f1f01db, 0x1f1f01c2, 0x1f1f01a9, 0x1f1f0190, + 0x1f1f0190, 0x1f1f0177, 0x1f1f015e, 0x1f1f015e, + 0x1f1f0145, 0x1f1f0145, 0x1f1f012c, 0x1f1f012c, + 0x1f1f012c, 0x1f1f0113, 0x1f1f0113, 0x1f1f0113, +}; + +static uint32_t axi_clkgen_lookup_lock(unsigned int m) +{ + if (m < ARRAY_SIZE(axi_clkgen_lock_table)) + return axi_clkgen_lock_table[m]; + return 0x1f1f00fa; +} + +static const unsigned int fpfd_min = 10000; +static const unsigned int fpfd_max = 300000; +static const unsigned int fvco_min = 600000; +static const unsigned int fvco_max = 1200000; + +static void axi_clkgen_calc_params(unsigned long fin, unsigned long fout, + unsigned int *best_d, unsigned int *best_m, unsigned int *best_dout) +{ + unsigned long d, d_min, d_max, _d_min, _d_max; + unsigned long m, m_min, m_max; + unsigned long f, dout, best_f, fvco; + + fin /= 1000; + fout /= 1000; + + best_f = ULONG_MAX; + *best_d = 0; + *best_m = 0; + *best_dout = 0; + + d_min = max_t(unsigned long, DIV_ROUND_UP(fin, fpfd_max), 1); + d_max = min_t(unsigned long, fin / fpfd_min, 80); + + m_min = max_t(unsigned long, DIV_ROUND_UP(fvco_min, fin) * d_min, 1); + m_max = min_t(unsigned long, fvco_max * d_max / fin, 64); + + for (m = m_min; m <= m_max; m++) { + _d_min = max(d_min, DIV_ROUND_UP(fin * m, fvco_max)); + _d_max = min(d_max, fin * m / fvco_min); + + for (d = _d_min; d <= _d_max; d++) { + fvco = fin * m / d; + + dout = DIV_ROUND_CLOSEST(fvco, fout); + dout = clamp_t(unsigned long, dout, 1, 128); + f = fvco / dout; + if (abs(f - fout) < abs(best_f - fout)) { + best_f = f; + *best_d = d; + *best_m = m; + *best_dout = dout; + if (best_f == fout) + return; + } + } + } +} + +static void axi_clkgen_calc_clk_params(unsigned int divider, unsigned int *low, + unsigned int *high, unsigned int *edge, unsigned int *nocount) +{ + if (divider == 1) + *nocount = 1; + else + *nocount = 0; + + *high = divider / 2; + *edge = divider % 2; + *low = divider - *high; +} + +static void axi_clkgen_write(struct axi_clkgen *axi_clkgen, + unsigned int reg, unsigned int val) +{ + writel(val, axi_clkgen->base + reg); +} + +static void axi_clkgen_read(struct axi_clkgen *axi_clkgen, + unsigned int reg, unsigned int *val) +{ + *val = readl(axi_clkgen->base + reg); +} + +static struct axi_clkgen *clk_hw_to_axi_clkgen(struct clk_hw *clk_hw) +{ + return container_of(clk_hw, struct axi_clkgen, clk_hw); +} + +static int axi_clkgen_set_rate(struct clk_hw *clk_hw, + unsigned long rate, unsigned long parent_rate) +{ + struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); + unsigned int d, m, dout; + unsigned int nocount; + unsigned int high; + unsigned int edge; + unsigned int low; + uint32_t filter; + uint32_t lock; + + if (parent_rate == 0 || rate == 0) + return -EINVAL; + + axi_clkgen_calc_params(parent_rate, rate, &d, &m, &dout); + + if (d == 0 || dout == 0 || m == 0) + return -EINVAL; + + filter = axi_clkgen_lookup_filter(m - 1); + lock = axi_clkgen_lookup_lock(m - 1); + + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_UPDATE_ENABLE, 0); + + axi_clkgen_calc_clk_params(dout, &low, &high, &edge, &nocount); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_CLK_OUT1, + (high << 6) | low); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_CLK_OUT2, + (edge << 7) | (nocount << 6)); + + axi_clkgen_calc_clk_params(d, &low, &high, &edge, &nocount); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_CLK_DIV, + (edge << 13) | (nocount << 12) | (high << 6) | low); + + axi_clkgen_calc_clk_params(m, &low, &high, &edge, &nocount); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_CLK_FB1, + (high << 6) | low); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_CLK_FB2, + (edge << 7) | (nocount << 6)); + + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_LOCK1, lock & 0x3ff); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_LOCK2, + (((lock >> 16) & 0x1f) << 10) | 0x1); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_LOCK3, + (((lock >> 24) & 0x1f) << 10) | 0x3e9); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_FILTER1, filter >> 16); + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_FILTER2, filter); + + axi_clkgen_write(axi_clkgen, AXI_CLKGEN_REG_UPDATE_ENABLE, 1); + + return 0; +} + +static long axi_clkgen_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned int d, m, dout; + + axi_clkgen_calc_params(*parent_rate, rate, &d, &m, &dout); + + if (d == 0 || dout == 0 || m == 0) + return -EINVAL; + + return *parent_rate / d * m / dout; +} + +static unsigned long axi_clkgen_recalc_rate(struct clk_hw *clk_hw, + unsigned long parent_rate) +{ + struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); + unsigned int d, m, dout; + unsigned int reg; + unsigned long long tmp; + + axi_clkgen_read(axi_clkgen, AXI_CLKGEN_REG_CLK_OUT1, ®); + dout = (reg & 0x3f) + ((reg >> 6) & 0x3f); + axi_clkgen_read(axi_clkgen, AXI_CLKGEN_REG_CLK_DIV, ®); + d = (reg & 0x3f) + ((reg >> 6) & 0x3f); + axi_clkgen_read(axi_clkgen, AXI_CLKGEN_REG_CLK_FB1, ®); + m = (reg & 0x3f) + ((reg >> 6) & 0x3f); + + if (d == 0 || dout == 0) + return 0; + + tmp = (unsigned long long)(parent_rate / d) * m; + do_div(tmp, dout); + + if (tmp > ULONG_MAX) + return ULONG_MAX; + + return tmp; +} + +static const struct clk_ops axi_clkgen_ops = { + .recalc_rate = axi_clkgen_recalc_rate, + .round_rate = axi_clkgen_round_rate, + .set_rate = axi_clkgen_set_rate, +}; + +static int axi_clkgen_probe(struct platform_device *pdev) +{ + struct axi_clkgen *axi_clkgen; + struct clk_init_data init; + const char *parent_name; + const char *clk_name; + struct resource *mem; + struct clk *clk; + + axi_clkgen = devm_kzalloc(&pdev->dev, sizeof(*axi_clkgen), GFP_KERNEL); + if (!axi_clkgen) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + axi_clkgen->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(axi_clkgen->base)) + return PTR_ERR(axi_clkgen->base); + + parent_name = of_clk_get_parent_name(pdev->dev.of_node, 0); + if (!parent_name) + return -EINVAL; + + clk_name = pdev->dev.of_node->name; + of_property_read_string(pdev->dev.of_node, "clock-output-names", + &clk_name); + + init.name = clk_name; + init.ops = &axi_clkgen_ops; + init.flags = 0; + init.parent_names = &parent_name; + init.num_parents = 1; + + axi_clkgen->clk_hw.init = &init; + clk = devm_clk_register(&pdev->dev, &axi_clkgen->clk_hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + return of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, + clk); +} + +static int axi_clkgen_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + + return 0; +} + +static const struct of_device_id axi_clkgen_ids[] = { + { .compatible = "adi,axi-clkgen-1.00.a" }, + { }, +}; +MODULE_DEVICE_TABLE(of, axi_clkgen_ids); + +static struct platform_driver axi_clkgen_driver = { + .driver = { + .name = "adi-axi-clkgen", + .owner = THIS_MODULE, + .of_match_table = axi_clkgen_ids, + }, + .probe = axi_clkgen_probe, + .remove = axi_clkgen_remove, +}; +module_platform_driver(axi_clkgen_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Driver for the Analog Devices' AXI clkgen pcore clock generator"); diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index ed87b2405806..253792a46c08 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -336,6 +336,31 @@ static inline int clk_debug_register(struct clk *clk) { return 0; } #endif /* caller must hold prepare_lock */ +static void clk_unprepare_unused_subtree(struct clk *clk) +{ + struct clk *child; + + if (!clk) + return; + + hlist_for_each_entry(child, &clk->children, child_node) + clk_unprepare_unused_subtree(child); + + if (clk->prepare_count) + return; + + if (clk->flags & CLK_IGNORE_UNUSED) + return; + + if (__clk_is_prepared(clk)) { + if (clk->ops->unprepare_unused) + clk->ops->unprepare_unused(clk->hw); + else if (clk->ops->unprepare) + clk->ops->unprepare(clk->hw); + } +} + +/* caller must hold prepare_lock */ static void clk_disable_unused_subtree(struct clk *clk) { struct clk *child; @@ -386,6 +411,12 @@ static int clk_disable_unused(void) hlist_for_each_entry(clk, &clk_orphan_list, child_node) clk_disable_unused_subtree(clk); + hlist_for_each_entry(clk, &clk_root_list, child_node) + clk_unprepare_unused_subtree(clk); + + hlist_for_each_entry(clk, &clk_orphan_list, child_node) + clk_unprepare_unused_subtree(clk); + mutex_unlock(&prepare_lock); return 0; @@ -451,6 +482,27 @@ unsigned long __clk_get_flags(struct clk *clk) return !clk ? 0 : clk->flags; } +bool __clk_is_prepared(struct clk *clk) +{ + int ret; + + if (!clk) + return false; + + /* + * .is_prepared is optional for clocks that can prepare + * fall back to software usage counter if it is missing + */ + if (!clk->ops->is_prepared) { + ret = clk->prepare_count ? 1 : 0; + goto out; + } + + ret = clk->ops->is_prepared(clk->hw); +out: + return !!ret; +} + bool __clk_is_enabled(struct clk *clk) { int ret; diff --git a/drivers/clk/mxs/clk.c b/drivers/clk/mxs/clk.c index b24d56067c80..5301bce8957b 100644 --- a/drivers/clk/mxs/clk.c +++ b/drivers/clk/mxs/clk.c @@ -13,6 +13,7 @@ #include <linux/io.h> #include <linux/jiffies.h> #include <linux/spinlock.h> +#include "clk.h" DEFINE_SPINLOCK(mxs_lock); diff --git a/drivers/clk/spear/spear1340_clock.c b/drivers/clk/spear/spear1340_clock.c index 3ceb4507e95f..9d0b3949db30 100644 --- a/drivers/clk/spear/spear1340_clock.c +++ b/drivers/clk/spear/spear1340_clock.c @@ -959,47 +959,47 @@ void __init spear1340_clk_init(void __iomem *misc_base) SPEAR1340_SPDIF_IN_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "d0100000.spdif-in"); - clk = clk_register_gate(NULL, "acp_clk", "acp_mclk", 0, + clk = clk_register_gate(NULL, "acp_clk", "ahb_clk", 0, SPEAR1340_PERIP2_CLK_ENB, SPEAR1340_ACP_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "acp_clk"); - clk = clk_register_gate(NULL, "plgpio_clk", "plgpio_mclk", 0, + clk = clk_register_gate(NULL, "plgpio_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_PLGPIO_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "e2800000.gpio"); - clk = clk_register_gate(NULL, "video_dec_clk", "video_dec_mclk", 0, + clk = clk_register_gate(NULL, "video_dec_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_VIDEO_DEC_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "video_dec"); - clk = clk_register_gate(NULL, "video_enc_clk", "video_enc_mclk", 0, + clk = clk_register_gate(NULL, "video_enc_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_VIDEO_ENC_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "video_enc"); - clk = clk_register_gate(NULL, "video_in_clk", "video_in_mclk", 0, + clk = clk_register_gate(NULL, "video_in_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_VIDEO_IN_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "spear_vip"); - clk = clk_register_gate(NULL, "cam0_clk", "cam0_mclk", 0, + clk = clk_register_gate(NULL, "cam0_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_CAM0_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "d0200000.cam0"); - clk = clk_register_gate(NULL, "cam1_clk", "cam1_mclk", 0, + clk = clk_register_gate(NULL, "cam1_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_CAM1_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "d0300000.cam1"); - clk = clk_register_gate(NULL, "cam2_clk", "cam2_mclk", 0, + clk = clk_register_gate(NULL, "cam2_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_CAM2_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "d0400000.cam2"); - clk = clk_register_gate(NULL, "cam3_clk", "cam3_mclk", 0, + clk = clk_register_gate(NULL, "cam3_clk", "ahb_clk", 0, SPEAR1340_PERIP3_CLK_ENB, SPEAR1340_CAM3_CLK_ENB, 0, &_lock); clk_register_clkdev(clk, NULL, "d0500000.cam3"); diff --git a/drivers/clk/ux500/clk-prcmu.c b/drivers/clk/ux500/clk-prcmu.c index 74faa7e3cf59..9d832567c6be 100644 --- a/drivers/clk/ux500/clk-prcmu.c +++ b/drivers/clk/ux500/clk-prcmu.c @@ -20,15 +20,23 @@ struct clk_prcmu { struct clk_hw hw; u8 cg_sel; + int is_prepared; int is_enabled; + int opp_requested; }; /* PRCMU clock operations. */ static int clk_prcmu_prepare(struct clk_hw *hw) { + int ret; struct clk_prcmu *clk = to_clk_prcmu(hw); - return prcmu_request_clock(clk->cg_sel, true); + + ret = prcmu_request_clock(clk->cg_sel, true); + if (!ret) + clk->is_prepared = 1; + + return ret;; } static void clk_prcmu_unprepare(struct clk_hw *hw) @@ -37,6 +45,14 @@ static void clk_prcmu_unprepare(struct clk_hw *hw) if (prcmu_request_clock(clk->cg_sel, false)) pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, hw->init->name); + else + clk->is_prepared = 0; +} + +static int clk_prcmu_is_prepared(struct clk_hw *hw) +{ + struct clk_prcmu *clk = to_clk_prcmu(hw); + return clk->is_prepared; } static int clk_prcmu_enable(struct clk_hw *hw) @@ -79,58 +95,52 @@ static int clk_prcmu_set_rate(struct clk_hw *hw, unsigned long rate, return prcmu_set_clock_rate(clk->cg_sel, rate); } -static int request_ape_opp100(bool enable) -{ - static int reqs; - int err = 0; - - if (enable) { - if (!reqs) - err = prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, - "clock", 100); - if (!err) - reqs++; - } else { - reqs--; - if (!reqs) - prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, - "clock"); - } - return err; -} - static int clk_prcmu_opp_prepare(struct clk_hw *hw) { int err; struct clk_prcmu *clk = to_clk_prcmu(hw); - err = request_ape_opp100(true); - if (err) { - pr_err("clk_prcmu: %s failed to request APE OPP100 for %s.\n", - __func__, hw->init->name); - return err; + if (!clk->opp_requested) { + err = prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + (char *)__clk_get_name(hw->clk), + 100); + if (err) { + pr_err("clk_prcmu: %s fail req APE OPP for %s.\n", + __func__, hw->init->name); + return err; + } + clk->opp_requested = 1; } err = prcmu_request_clock(clk->cg_sel, true); - if (err) - request_ape_opp100(false); + if (err) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, + (char *)__clk_get_name(hw->clk)); + clk->opp_requested = 0; + return err; + } - return err; + clk->is_prepared = 1; + return 0; } static void clk_prcmu_opp_unprepare(struct clk_hw *hw) { struct clk_prcmu *clk = to_clk_prcmu(hw); - if (prcmu_request_clock(clk->cg_sel, false)) - goto out_error; - if (request_ape_opp100(false)) - goto out_error; - return; - -out_error: - pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, - hw->init->name); + if (prcmu_request_clock(clk->cg_sel, false)) { + pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, + hw->init->name); + return; + } + + if (clk->opp_requested) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, + (char *)__clk_get_name(hw->clk)); + clk->opp_requested = 0; + } + + clk->is_prepared = 0; } static int clk_prcmu_opp_volt_prepare(struct clk_hw *hw) @@ -138,38 +148,49 @@ static int clk_prcmu_opp_volt_prepare(struct clk_hw *hw) int err; struct clk_prcmu *clk = to_clk_prcmu(hw); - err = prcmu_request_ape_opp_100_voltage(true); - if (err) { - pr_err("clk_prcmu: %s failed to request APE OPP VOLT for %s.\n", - __func__, hw->init->name); - return err; + if (!clk->opp_requested) { + err = prcmu_request_ape_opp_100_voltage(true); + if (err) { + pr_err("clk_prcmu: %s fail req APE OPP VOLT for %s.\n", + __func__, hw->init->name); + return err; + } + clk->opp_requested = 1; } err = prcmu_request_clock(clk->cg_sel, true); - if (err) + if (err) { prcmu_request_ape_opp_100_voltage(false); + clk->opp_requested = 0; + return err; + } - return err; + clk->is_prepared = 1; + return 0; } static void clk_prcmu_opp_volt_unprepare(struct clk_hw *hw) { struct clk_prcmu *clk = to_clk_prcmu(hw); - if (prcmu_request_clock(clk->cg_sel, false)) - goto out_error; - if (prcmu_request_ape_opp_100_voltage(false)) - goto out_error; - return; - -out_error: - pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, - hw->init->name); + if (prcmu_request_clock(clk->cg_sel, false)) { + pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, + hw->init->name); + return; + } + + if (clk->opp_requested) { + prcmu_request_ape_opp_100_voltage(false); + clk->opp_requested = 0; + } + + clk->is_prepared = 0; } static struct clk_ops clk_prcmu_scalable_ops = { .prepare = clk_prcmu_prepare, .unprepare = clk_prcmu_unprepare, + .is_prepared = clk_prcmu_is_prepared, .enable = clk_prcmu_enable, .disable = clk_prcmu_disable, .is_enabled = clk_prcmu_is_enabled, @@ -181,6 +202,7 @@ static struct clk_ops clk_prcmu_scalable_ops = { static struct clk_ops clk_prcmu_gate_ops = { .prepare = clk_prcmu_prepare, .unprepare = clk_prcmu_unprepare, + .is_prepared = clk_prcmu_is_prepared, .enable = clk_prcmu_enable, .disable = clk_prcmu_disable, .is_enabled = clk_prcmu_is_enabled, @@ -202,6 +224,7 @@ static struct clk_ops clk_prcmu_rate_ops = { static struct clk_ops clk_prcmu_opp_gate_ops = { .prepare = clk_prcmu_opp_prepare, .unprepare = clk_prcmu_opp_unprepare, + .is_prepared = clk_prcmu_is_prepared, .enable = clk_prcmu_enable, .disable = clk_prcmu_disable, .is_enabled = clk_prcmu_is_enabled, @@ -211,6 +234,7 @@ static struct clk_ops clk_prcmu_opp_gate_ops = { static struct clk_ops clk_prcmu_opp_volt_scalable_ops = { .prepare = clk_prcmu_opp_volt_prepare, .unprepare = clk_prcmu_opp_volt_unprepare, + .is_prepared = clk_prcmu_is_prepared, .enable = clk_prcmu_enable, .disable = clk_prcmu_disable, .is_enabled = clk_prcmu_is_enabled, @@ -242,7 +266,9 @@ static struct clk *clk_reg_prcmu(const char *name, } clk->cg_sel = cg_sel; + clk->is_prepared = 1; clk->is_enabled = 1; + clk->opp_requested = 0; /* "rate" can be used for changing the initial frequency */ if (rate) prcmu_set_clock_rate(cg_sel, rate); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 7f197d7addb0..56e6cc12c796 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -45,6 +45,14 @@ struct clk_hw; * undo any work done in the @prepare callback. Called with * prepare_lock held. * + * @is_prepared: Queries the hardware to determine if the clock is prepared. + * This function is allowed to sleep. Optional, if this op is not + * set then the prepare count will be used. + * + * @unprepare_unused: Unprepare the clock atomically. Only called from + * clk_disable_unused for prepare clocks with special needs. + * Called with prepare mutex held. This function may sleep. + * * @enable: Enable the clock atomically. This must not return until the * clock is generating a valid clock signal, usable by consumer * devices. Called with enable_lock held. This function must not @@ -108,6 +116,8 @@ struct clk_hw; struct clk_ops { int (*prepare)(struct clk_hw *hw); void (*unprepare)(struct clk_hw *hw); + int (*is_prepared)(struct clk_hw *hw); + void (*unprepare_unused)(struct clk_hw *hw); int (*enable)(struct clk_hw *hw); void (*disable)(struct clk_hw *hw); int (*is_enabled)(struct clk_hw *hw); @@ -351,6 +361,7 @@ unsigned int __clk_get_enable_count(struct clk *clk); unsigned int __clk_get_prepare_count(struct clk *clk); unsigned long __clk_get_rate(struct clk *clk); unsigned long __clk_get_flags(struct clk *clk); +bool __clk_is_prepared(struct clk *clk); bool __clk_is_enabled(struct clk *clk); struct clk *__clk_lookup(const char *name); |