clk: msm: Add snapshot of clock framework files
This is snapshot of the clock framework files as of msm-3.18
'commit c0b3f609196f ("ARM: dts: msm: Remove GPU mempool
for msm8909")'.
Below is the brief description of the additional changes made:
1. Add COMMON_CLK_MSM config flag for conditional compilation for
some common files used between COMMON_CLK_MSM and COMMON_CLK_QCOM
clock framework files.
2. Add reset controller framework files for BCR operation.
3. Add conditional compilation support for FTRACE clock functions
to maintain compatibility for clock framework based on
COMMON_CLK_MSM and COMMON_CLK_QCOM.
4. Add files for GDSC operation.
Change-Id: Ia5688600ca8e548beb15745d3ce938fdf41f82de
Signed-off-by: Shefali Jain <shefjain@codeaurora.org>
This commit is contained in:
parent
04eaabc134
commit
0dc6e78a3f
38 changed files with 12743 additions and 35 deletions
|
|
@ -0,0 +1,22 @@
|
|||
Qualcomm Technologies MSM Clock Controller
|
||||
|
||||
Required properties :
|
||||
- compatible : shall contain "qcom,msm-clock-controller"
|
||||
- reg : shall contain base register location and length
|
||||
- reg-names: names of registers listed in the same order as in
|
||||
the reg property.
|
||||
- #clock-cells : shall contain 1
|
||||
- #reset-cells : shall contain 1
|
||||
|
||||
Optional properties :
|
||||
- vdd_<rail>-supply: The logic rail supply.
|
||||
|
||||
Example:
|
||||
clock_gcc: qcom,gcc@1800000 {
|
||||
compatible = "qcom,msm-clock-controller";
|
||||
reg = <0x1800000 0x80000>;
|
||||
reg-names = "cc-base";
|
||||
#clock-cells = <1>;
|
||||
clock-names = "a7_debug_clk";
|
||||
clocks = <&clock_a7pll clk_a7_debug_mux>;
|
||||
};
|
||||
|
|
@ -212,3 +212,5 @@ source "drivers/clk/ti/Kconfig"
|
|||
source "drivers/clk/uniphier/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
source "drivers/clk/msm/Kconfig"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# common clock types
|
||||
obj-$(CONFIG_HAVE_CLK) += clk-devres.o
|
||||
obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o
|
||||
obj-$(CONFIG_COMMON_CLK) += clk.o
|
||||
obj-$(CONFIG_OF) += clk.o
|
||||
obj-$(CONFIG_COMMON_CLK) += clk-divider.o
|
||||
obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o
|
||||
obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o
|
||||
|
|
@ -92,3 +92,4 @@ obj-$(CONFIG_X86) += x86/
|
|||
endif
|
||||
obj-$(CONFIG_ARCH_ZX) += zte/
|
||||
obj-$(CONFIG_ARCH_ZYNQ) += zynq/
|
||||
obj-$(CONFIG_ARCH_QCOM) += msm/
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
#include "clk.h"
|
||||
|
||||
#if defined(CONFIG_COMMON_CLK)
|
||||
|
||||
static DEFINE_SPINLOCK(enable_lock);
|
||||
static DEFINE_MUTEX(prepare_lock);
|
||||
|
||||
|
|
@ -4032,6 +4034,8 @@ int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(clk_notifier_unregister);
|
||||
|
||||
#endif /* CONFIG_COMMON_CLK */
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
/**
|
||||
* struct of_clk_provider - Clock provider registration structure
|
||||
|
|
@ -4069,6 +4073,8 @@ struct clk_hw *of_clk_hw_simple_get(struct of_phandle_args *clkspec, void *data)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(of_clk_hw_simple_get);
|
||||
|
||||
#if defined(CONFIG_COMMON_CLK)
|
||||
|
||||
struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data)
|
||||
{
|
||||
struct clk_onecell_data *clk_data = data;
|
||||
|
|
@ -4098,6 +4104,29 @@ of_clk_hw_onecell_get(struct of_phandle_args *clkspec, void *data)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(of_clk_hw_onecell_get);
|
||||
|
||||
#endif /* CONFIG_COMMON_CLK */
|
||||
|
||||
/**
|
||||
* of_clk_del_provider() - Remove a previously registered clock provider
|
||||
* @np: Device node pointer associated with clock provider
|
||||
*/
|
||||
void of_clk_del_provider(struct device_node *np)
|
||||
{
|
||||
struct of_clk_provider *cp;
|
||||
|
||||
mutex_lock(&of_clk_mutex);
|
||||
list_for_each_entry(cp, &of_clk_providers, link) {
|
||||
if (cp->node == np) {
|
||||
list_del(&cp->link);
|
||||
of_node_put(cp->node);
|
||||
kfree(cp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&of_clk_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_clk_del_provider);
|
||||
|
||||
/**
|
||||
* of_clk_add_provider() - Register a clock provider for a node
|
||||
* @np: Device node pointer associated with clock provider
|
||||
|
|
@ -4168,27 +4197,6 @@ int of_clk_add_hw_provider(struct device_node *np,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(of_clk_add_hw_provider);
|
||||
|
||||
/**
|
||||
* of_clk_del_provider() - Remove a previously registered clock provider
|
||||
* @np: Device node pointer associated with clock provider
|
||||
*/
|
||||
void of_clk_del_provider(struct device_node *np)
|
||||
{
|
||||
struct of_clk_provider *cp;
|
||||
|
||||
mutex_lock(&of_clk_mutex);
|
||||
list_for_each_entry(cp, &of_clk_providers, link) {
|
||||
if (cp->node == np) {
|
||||
list_del(&cp->link);
|
||||
of_node_put(cp->node);
|
||||
kfree(cp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&of_clk_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_clk_del_provider);
|
||||
|
||||
static struct clk_hw *
|
||||
__of_clk_get_hw_from_provider(struct of_clk_provider *provider,
|
||||
struct of_phandle_args *clkspec)
|
||||
|
|
@ -4317,8 +4325,10 @@ const char *of_clk_get_parent_name(struct device_node *np, int index)
|
|||
else
|
||||
clk_name = NULL;
|
||||
} else {
|
||||
#if defined(CONFIG_COMMON_CLK)
|
||||
clk_name = __clk_get_name(clk);
|
||||
clk_put(clk);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4349,6 +4359,8 @@ int of_clk_parent_fill(struct device_node *np, const char **parents,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(of_clk_parent_fill);
|
||||
|
||||
#if defined(CONFIG_COMMON_CLK)
|
||||
|
||||
struct clock_provider {
|
||||
of_clk_init_cb_t clk_init_cb;
|
||||
struct device_node *np;
|
||||
|
|
@ -4499,4 +4511,7 @@ void __init of_clk_init(const struct of_device_id *matches)
|
|||
force = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_COMMON_CLK */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
struct clk_hw;
|
||||
struct clk_core;
|
||||
|
||||
#if defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK)
|
||||
#if defined(CONFIG_OF)
|
||||
struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,
|
||||
const char *dev_id, const char *con_id);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
static LIST_HEAD(clocks);
|
||||
static DEFINE_MUTEX(clocks_mutex);
|
||||
|
||||
#if defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK)
|
||||
#if defined(CONFIG_OF)
|
||||
static struct clk *__of_clk_get(struct device_node *np, int index,
|
||||
const char *dev_id, const char *con_id)
|
||||
{
|
||||
|
|
@ -73,14 +73,10 @@ static struct clk *__of_clk_get_by_name(struct device_node *np,
|
|||
if (name)
|
||||
index = of_property_match_string(np, "clock-names", name);
|
||||
clk = __of_clk_get(np, index, dev_id, name);
|
||||
if (!IS_ERR(clk)) {
|
||||
if (!IS_ERR(clk))
|
||||
break;
|
||||
} else if (name && index >= 0) {
|
||||
if (PTR_ERR(clk) != -EPROBE_DEFER)
|
||||
pr_err("ERROR: could not get clock %s:%s(%i)\n",
|
||||
np->full_name, name ? name : "", index);
|
||||
else if (name && index >= 0)
|
||||
return clk;
|
||||
}
|
||||
|
||||
/*
|
||||
* No matching clock found on this node. If the parent node
|
||||
|
|
@ -190,7 +186,7 @@ struct clk *clk_get_sys(const char *dev_id, const char *con_id)
|
|||
out:
|
||||
mutex_unlock(&clocks_mutex);
|
||||
|
||||
return cl ? clk : ERR_PTR(-ENOENT);
|
||||
return cl ? cl->clk : ERR_PTR(-ENOENT);
|
||||
}
|
||||
EXPORT_SYMBOL(clk_get_sys);
|
||||
|
||||
|
|
|
|||
18
drivers/clk/msm/Kconfig
Normal file
18
drivers/clk/msm/Kconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
config COMMON_CLK_MSM
|
||||
tristate "Support for MSM clock controllers"
|
||||
depends on OF
|
||||
depends on ARCH_QCOM
|
||||
select RATIONAL
|
||||
help
|
||||
This support clock controller used by MSM devices which support
|
||||
global, mmss and gpu clock controller.
|
||||
Say Y if you want to support the clocks exposed by the MSM on
|
||||
platforms such as msm8953 etc.
|
||||
|
||||
config MSM_CLK_CONTROLLER_V2
|
||||
bool "QTI clock driver"
|
||||
depends on COMMON_CLK_MSM
|
||||
---help---
|
||||
Generate clock data structures from definitions found in
|
||||
device tree.
|
||||
|
||||
16
drivers/clk/msm/Makefile
Normal file
16
drivers/clk/msm/Makefile
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
obj-$(CONFIG_COMMON_CLK_MSM) += clock.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-dummy.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-generic.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-local2.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-pll.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-alpha-pll.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-rpm.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-voter.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += reset.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += clock-debug.o
|
||||
obj-$(CONFIG_COMMON_CLK_MSM) += gdsc.o
|
||||
|
||||
obj-$(CONFIG_MSM_CLK_CONTROLLER_V2) += msm-clock-controller.o
|
||||
|
||||
|
||||
|
||||
1265
drivers/clk/msm/clock-alpha-pll.c
Normal file
1265
drivers/clk/msm/clock-alpha-pll.c
Normal file
File diff suppressed because it is too large
Load diff
721
drivers/clk/msm/clock-debug.c
Normal file
721
drivers/clk/msm/clock-debug.c
Normal file
|
|
@ -0,0 +1,721 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
* Copyright (c) 2007-2014, 2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk/msm-clk-provider.h>
|
||||
#include <trace/events/power.h>
|
||||
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
static LIST_HEAD(clk_list);
|
||||
static DEFINE_MUTEX(clk_list_lock);
|
||||
|
||||
static struct dentry *debugfs_base;
|
||||
static u32 debug_suspend;
|
||||
|
||||
static int clock_debug_rate_set(void *data, u64 val)
|
||||
{
|
||||
struct clk *clock = data;
|
||||
int ret;
|
||||
|
||||
/* Only increases to max rate will succeed, but that's actually good
|
||||
* for debugging purposes so we don't check for error.
|
||||
*/
|
||||
if (clock->flags & CLKFLAG_MAX)
|
||||
clk_set_max_rate(clock, val);
|
||||
ret = clk_set_rate(clock, val);
|
||||
if (ret)
|
||||
pr_err("clk_set_rate(%s, %lu) failed (%d)\n", clock->dbg_name,
|
||||
(unsigned long)val, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int clock_debug_rate_get(void *data, u64 *val)
|
||||
{
|
||||
struct clk *clock = data;
|
||||
*val = clk_get_rate(clock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get,
|
||||
clock_debug_rate_set, "%llu\n");
|
||||
|
||||
static struct clk *measure;
|
||||
|
||||
static int clock_debug_measure_get(void *data, u64 *val)
|
||||
{
|
||||
struct clk *clock = data, *par;
|
||||
int ret, is_hw_gated;
|
||||
unsigned long meas_rate, sw_rate;
|
||||
|
||||
/* Check to see if the clock is in hardware gating mode */
|
||||
if (clock->ops->in_hwcg_mode)
|
||||
is_hw_gated = clock->ops->in_hwcg_mode(clock);
|
||||
else
|
||||
is_hw_gated = 0;
|
||||
|
||||
ret = clk_set_parent(measure, clock);
|
||||
if (!ret) {
|
||||
/*
|
||||
* Disable hw gating to get accurate rate measurements. Only do
|
||||
* this if the clock is explicitly enabled by software. This
|
||||
* allows us to detect errors where clocks are on even though
|
||||
* software is not requesting them to be on due to broken
|
||||
* hardware gating signals.
|
||||
*/
|
||||
if (is_hw_gated && clock->count)
|
||||
clock->ops->disable_hwcg(clock);
|
||||
par = measure;
|
||||
while (par && par != clock) {
|
||||
if (par->ops->enable)
|
||||
par->ops->enable(par);
|
||||
par = par->parent;
|
||||
}
|
||||
*val = clk_get_rate(measure);
|
||||
/* Reenable hwgating if it was disabled */
|
||||
if (is_hw_gated && clock->count)
|
||||
clock->ops->enable_hwcg(clock);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there's a divider on the path from the clock output to the
|
||||
* measurement circuitry, account for it by dividing the original clock
|
||||
* rate with the rate set on the parent of the measure clock.
|
||||
*/
|
||||
meas_rate = clk_get_rate(clock);
|
||||
sw_rate = clk_get_rate(measure->parent);
|
||||
if (sw_rate && meas_rate >= (sw_rate * 2))
|
||||
*val *= DIV_ROUND_CLOSEST(meas_rate, sw_rate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(clock_measure_fops, clock_debug_measure_get,
|
||||
NULL, "%lld\n");
|
||||
|
||||
static int clock_debug_enable_set(void *data, u64 val)
|
||||
{
|
||||
struct clk *clock = data;
|
||||
int rc = 0;
|
||||
|
||||
if (val)
|
||||
rc = clk_prepare_enable(clock);
|
||||
else
|
||||
clk_disable_unprepare(clock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int clock_debug_enable_get(void *data, u64 *val)
|
||||
{
|
||||
struct clk *clock = data;
|
||||
int enabled;
|
||||
|
||||
if (clock->ops->is_enabled)
|
||||
enabled = clock->ops->is_enabled(clock);
|
||||
else
|
||||
enabled = !!(clock->count);
|
||||
|
||||
*val = enabled;
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get,
|
||||
clock_debug_enable_set, "%lld\n");
|
||||
|
||||
static int clock_debug_local_get(void *data, u64 *val)
|
||||
{
|
||||
struct clk *clock = data;
|
||||
|
||||
if (!clock->ops->is_local)
|
||||
*val = true;
|
||||
else
|
||||
*val = clock->ops->is_local(clock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(clock_local_fops, clock_debug_local_get,
|
||||
NULL, "%llu\n");
|
||||
|
||||
static int clock_debug_hwcg_get(void *data, u64 *val)
|
||||
{
|
||||
struct clk *clock = data;
|
||||
|
||||
if (clock->ops->in_hwcg_mode)
|
||||
*val = !!clock->ops->in_hwcg_mode(clock);
|
||||
else
|
||||
*val = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(clock_hwcg_fops, clock_debug_hwcg_get,
|
||||
NULL, "%llu\n");
|
||||
|
||||
static void clock_print_fmax_by_level(struct seq_file *m, int level)
|
||||
{
|
||||
struct clk *clock = m->private;
|
||||
struct clk_vdd_class *vdd_class = clock->vdd_class;
|
||||
int off, i, vdd_level, nregs = vdd_class->num_regulators;
|
||||
|
||||
vdd_level = find_vdd_level(clock, clock->rate);
|
||||
|
||||
seq_printf(m, "%2s%10lu", vdd_level == level ? "[" : "",
|
||||
clock->fmax[level]);
|
||||
for (i = 0; i < nregs; i++) {
|
||||
off = nregs*level + i;
|
||||
if (vdd_class->vdd_uv)
|
||||
seq_printf(m, "%10u", vdd_class->vdd_uv[off]);
|
||||
if (vdd_class->vdd_ua)
|
||||
seq_printf(m, "%10u", vdd_class->vdd_ua[off]);
|
||||
}
|
||||
|
||||
if (vdd_level == level)
|
||||
seq_puts(m, "]");
|
||||
seq_puts(m, "\n");
|
||||
}
|
||||
|
||||
static int fmax_rates_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
struct clk *clock = m->private;
|
||||
struct clk_vdd_class *vdd_class = clock->vdd_class;
|
||||
int level = 0, i, nregs = vdd_class->num_regulators;
|
||||
char reg_name[10];
|
||||
|
||||
int vdd_level = find_vdd_level(clock, clock->rate);
|
||||
|
||||
if (vdd_level < 0) {
|
||||
seq_printf(m, "could not find_vdd_level for %s, %ld\n",
|
||||
clock->dbg_name, clock->rate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
seq_printf(m, "%12s", "");
|
||||
for (i = 0; i < nregs; i++) {
|
||||
snprintf(reg_name, ARRAY_SIZE(reg_name), "reg %d", i);
|
||||
seq_printf(m, "%10s", reg_name);
|
||||
if (vdd_class->vdd_ua)
|
||||
seq_printf(m, "%10s", "");
|
||||
}
|
||||
|
||||
seq_printf(m, "\n%12s", "freq");
|
||||
for (i = 0; i < nregs; i++) {
|
||||
seq_printf(m, "%10s", "uV");
|
||||
if (vdd_class->vdd_ua)
|
||||
seq_printf(m, "%10s", "uA");
|
||||
}
|
||||
seq_puts(m, "\n");
|
||||
|
||||
for (level = 0; level < clock->num_fmax; level++)
|
||||
clock_print_fmax_by_level(m, level);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fmax_rates_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, fmax_rates_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations fmax_rates_fops = {
|
||||
.open = fmax_rates_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
static int orphan_list_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
struct clk *c, *safe;
|
||||
|
||||
list_for_each_entry_safe(c, safe, &orphan_clk_list, list)
|
||||
seq_printf(m, "%s\n", c->dbg_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int orphan_list_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, orphan_list_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations orphan_list_fops = {
|
||||
.open = orphan_list_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
#define clock_debug_output(m, c, fmt, ...) \
|
||||
do { \
|
||||
if (m) \
|
||||
seq_printf(m, fmt, ##__VA_ARGS__); \
|
||||
else if (c) \
|
||||
pr_cont(fmt, ##__VA_ARGS__); \
|
||||
else \
|
||||
pr_info(fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* clock_debug_print_enabled_debug_suspend() - Print names of enabled clocks
|
||||
* during suspend.
|
||||
*/
|
||||
static void clock_debug_print_enabled_debug_suspend(struct seq_file *s)
|
||||
{
|
||||
struct clk *c;
|
||||
int cnt = 0;
|
||||
|
||||
if (!mutex_trylock(&clk_list_lock))
|
||||
return;
|
||||
|
||||
clock_debug_output(s, 0, "Enabled clocks:\n");
|
||||
|
||||
list_for_each_entry(c, &clk_list, list) {
|
||||
if (!c || !c->prepare_count)
|
||||
continue;
|
||||
if (c->vdd_class)
|
||||
clock_debug_output(s, 0, " %s:%lu:%lu [%ld, %d]",
|
||||
c->dbg_name, c->prepare_count,
|
||||
c->count, c->rate,
|
||||
find_vdd_level(c, c->rate));
|
||||
else
|
||||
clock_debug_output(s, 0, " %s:%lu:%lu [%ld]",
|
||||
c->dbg_name, c->prepare_count,
|
||||
c->count, c->rate);
|
||||
cnt++;
|
||||
}
|
||||
|
||||
mutex_unlock(&clk_list_lock);
|
||||
|
||||
if (cnt)
|
||||
clock_debug_output(s, 0, "Enabled clock count: %d\n", cnt);
|
||||
else
|
||||
clock_debug_output(s, 0, "No clocks enabled.\n");
|
||||
}
|
||||
|
||||
static int clock_debug_print_clock(struct clk *c, struct seq_file *m)
|
||||
{
|
||||
char *start = "";
|
||||
|
||||
if (!c || !c->prepare_count)
|
||||
return 0;
|
||||
|
||||
clock_debug_output(m, 0, "\t");
|
||||
do {
|
||||
if (c->vdd_class)
|
||||
clock_debug_output(m, 1, "%s%s:%lu:%lu [%ld, %d]",
|
||||
start, c->dbg_name, c->prepare_count, c->count,
|
||||
c->rate, find_vdd_level(c, c->rate));
|
||||
else
|
||||
clock_debug_output(m, 1, "%s%s:%lu:%lu [%ld]", start,
|
||||
c->dbg_name, c->prepare_count, c->count,
|
||||
c->rate);
|
||||
start = " -> ";
|
||||
} while ((c = clk_get_parent(c)));
|
||||
|
||||
clock_debug_output(m, 1, "\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* clock_debug_print_enabled_clocks() - Print names of enabled clocks
|
||||
*
|
||||
*/
|
||||
static void clock_debug_print_enabled_clocks(struct seq_file *m)
|
||||
{
|
||||
struct clk *c;
|
||||
int cnt = 0;
|
||||
|
||||
if (!mutex_trylock(&clk_list_lock)) {
|
||||
pr_err("clock-debug: Clocks are being registered. Cannot print clock state now.\n");
|
||||
return;
|
||||
}
|
||||
clock_debug_output(m, 0, "Enabled clocks:\n");
|
||||
list_for_each_entry(c, &clk_list, list) {
|
||||
cnt += clock_debug_print_clock(c, m);
|
||||
}
|
||||
mutex_unlock(&clk_list_lock);
|
||||
|
||||
if (cnt)
|
||||
clock_debug_output(m, 0, "Enabled clock count: %d\n", cnt);
|
||||
else
|
||||
clock_debug_output(m, 0, "No clocks enabled.\n");
|
||||
}
|
||||
|
||||
static int enabled_clocks_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
clock_debug_print_enabled_clocks(m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enabled_clocks_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, enabled_clocks_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations enabled_clocks_fops = {
|
||||
.open = enabled_clocks_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
static int trace_clocks_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
struct clk *c;
|
||||
int total_cnt = 0;
|
||||
|
||||
if (!mutex_trylock(&clk_list_lock)) {
|
||||
pr_err("trace_clocks: Clocks are being registered. Cannot trace clock state now.\n");
|
||||
return 1;
|
||||
}
|
||||
list_for_each_entry(c, &clk_list, list) {
|
||||
trace_clock_state(c->dbg_name, c->prepare_count, c->count,
|
||||
c->rate);
|
||||
total_cnt++;
|
||||
}
|
||||
mutex_unlock(&clk_list_lock);
|
||||
clock_debug_output(m, 0, "Total clock count: %d\n", total_cnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int trace_clocks_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, trace_clocks_show, inode->i_private);
|
||||
}
|
||||
static const struct file_operations trace_clocks_fops = {
|
||||
.open = trace_clocks_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
static int list_rates_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
struct clk *clock = m->private;
|
||||
int level, i = 0;
|
||||
unsigned long rate, fmax = 0;
|
||||
|
||||
/* Find max frequency supported within voltage constraints. */
|
||||
if (!clock->vdd_class) {
|
||||
fmax = ULONG_MAX;
|
||||
} else {
|
||||
for (level = 0; level < clock->num_fmax; level++)
|
||||
if (clock->fmax[level])
|
||||
fmax = clock->fmax[level];
|
||||
}
|
||||
|
||||
/*
|
||||
* List supported frequencies <= fmax. Higher frequencies may appear in
|
||||
* the frequency table, but are not valid and should not be listed.
|
||||
*/
|
||||
while (!IS_ERR_VALUE(rate = clock->ops->list_rate(clock, i++))) {
|
||||
if (rate <= fmax)
|
||||
seq_printf(m, "%lu\n", rate);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int list_rates_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, list_rates_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations list_rates_fops = {
|
||||
.open = list_rates_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
static ssize_t clock_parent_read(struct file *filp, char __user *ubuf,
|
||||
size_t cnt, loff_t *ppos)
|
||||
{
|
||||
struct clk *clock = filp->private_data;
|
||||
struct clk *p = clock->parent;
|
||||
char name[256] = {0};
|
||||
|
||||
snprintf(name, sizeof(name), "%s\n", p ? p->dbg_name : "None\n");
|
||||
|
||||
return simple_read_from_buffer(ubuf, cnt, ppos, name, strlen(name));
|
||||
}
|
||||
|
||||
|
||||
static ssize_t clock_parent_write(struct file *filp,
|
||||
const char __user *ubuf, size_t cnt, loff_t *ppos)
|
||||
{
|
||||
struct clk *clock = filp->private_data;
|
||||
char buf[256];
|
||||
char *cmp;
|
||||
int ret;
|
||||
struct clk *parent = NULL;
|
||||
|
||||
cnt = min(cnt, sizeof(buf) - 1);
|
||||
if (copy_from_user(&buf, ubuf, cnt))
|
||||
return -EFAULT;
|
||||
buf[cnt] = '\0';
|
||||
cmp = strstrip(buf);
|
||||
|
||||
mutex_lock(&clk_list_lock);
|
||||
list_for_each_entry(parent, &clk_list, list) {
|
||||
if (!strcmp(cmp, parent->dbg_name))
|
||||
break;
|
||||
}
|
||||
|
||||
if (&parent->list == &clk_list) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_unlock(&clk_list_lock);
|
||||
ret = clk_set_parent(clock, parent);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return cnt;
|
||||
err:
|
||||
mutex_unlock(&clk_list_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static const struct file_operations clock_parent_fops = {
|
||||
.open = simple_open,
|
||||
.read = clock_parent_read,
|
||||
.write = clock_parent_write,
|
||||
};
|
||||
|
||||
void clk_debug_print_hw(struct clk *clk, struct seq_file *f)
|
||||
{
|
||||
void __iomem *base;
|
||||
struct clk_register_data *regs;
|
||||
u32 i, j, size;
|
||||
|
||||
if (IS_ERR_OR_NULL(clk))
|
||||
return;
|
||||
|
||||
clk_debug_print_hw(clk->parent, f);
|
||||
|
||||
clock_debug_output(f, false, "%s\n", clk->dbg_name);
|
||||
|
||||
if (!clk->ops->list_registers)
|
||||
return;
|
||||
|
||||
j = 0;
|
||||
base = clk->ops->list_registers(clk, j, ®s, &size);
|
||||
while (!IS_ERR(base)) {
|
||||
for (i = 0; i < size; i++) {
|
||||
u32 val = readl_relaxed(base + regs[i].offset);
|
||||
|
||||
clock_debug_output(f, false, "%20s: 0x%.8x\n",
|
||||
regs[i].name, val);
|
||||
}
|
||||
j++;
|
||||
base = clk->ops->list_registers(clk, j, ®s, &size);
|
||||
}
|
||||
}
|
||||
|
||||
static int print_hw_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
struct clk *c = m->private;
|
||||
|
||||
clk_debug_print_hw(c, m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int print_hw_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, print_hw_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations clock_print_hw_fops = {
|
||||
.open = print_hw_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
|
||||
static void clock_measure_add(struct clk *clock)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(measure))
|
||||
return;
|
||||
|
||||
if (clk_set_parent(measure, clock))
|
||||
return;
|
||||
|
||||
debugfs_create_file("measure", 0444, clock->clk_dir, clock,
|
||||
&clock_measure_fops);
|
||||
}
|
||||
|
||||
static int clock_debug_add(struct clk *clock)
|
||||
{
|
||||
char temp[50], *ptr;
|
||||
struct dentry *clk_dir;
|
||||
|
||||
if (!debugfs_base)
|
||||
return -ENOMEM;
|
||||
|
||||
strlcpy(temp, clock->dbg_name, ARRAY_SIZE(temp));
|
||||
for (ptr = temp; *ptr; ptr++)
|
||||
*ptr = tolower(*ptr);
|
||||
|
||||
clk_dir = debugfs_create_dir(temp, debugfs_base);
|
||||
if (!clk_dir)
|
||||
return -ENOMEM;
|
||||
|
||||
clock->clk_dir = clk_dir;
|
||||
|
||||
if (!debugfs_create_file("rate", 0644, clk_dir,
|
||||
clock, &clock_rate_fops))
|
||||
goto error;
|
||||
|
||||
if (!debugfs_create_file("enable", 0644, clk_dir,
|
||||
clock, &clock_enable_fops))
|
||||
goto error;
|
||||
|
||||
if (!debugfs_create_file("is_local", 0444, clk_dir, clock,
|
||||
&clock_local_fops))
|
||||
goto error;
|
||||
|
||||
if (!debugfs_create_file("has_hw_gating", 0444, clk_dir, clock,
|
||||
&clock_hwcg_fops))
|
||||
goto error;
|
||||
|
||||
if (clock->ops->list_rate)
|
||||
if (!debugfs_create_file("list_rates",
|
||||
0444, clk_dir, clock, &list_rates_fops))
|
||||
goto error;
|
||||
|
||||
if (clock->vdd_class && !debugfs_create_file(
|
||||
"fmax_rates", 0444, clk_dir, clock, &fmax_rates_fops))
|
||||
goto error;
|
||||
|
||||
if (!debugfs_create_file("parent", 0444, clk_dir, clock,
|
||||
&clock_parent_fops))
|
||||
goto error;
|
||||
|
||||
if (!debugfs_create_file("print", 0444, clk_dir, clock,
|
||||
&clock_print_hw_fops))
|
||||
goto error;
|
||||
|
||||
clock_measure_add(clock);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
debugfs_remove_recursive(clk_dir);
|
||||
return -ENOMEM;
|
||||
}
|
||||
static DEFINE_MUTEX(clk_debug_lock);
|
||||
static int clk_debug_init_once;
|
||||
|
||||
/**
|
||||
* clock_debug_init() - Initialize clock debugfs
|
||||
* Lock clk_debug_lock before invoking this function.
|
||||
*/
|
||||
static int clock_debug_init(void)
|
||||
{
|
||||
if (clk_debug_init_once)
|
||||
return 0;
|
||||
|
||||
clk_debug_init_once = 1;
|
||||
|
||||
debugfs_base = debugfs_create_dir("clk", NULL);
|
||||
if (!debugfs_base)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!debugfs_create_u32("debug_suspend", 0644,
|
||||
debugfs_base, &debug_suspend)) {
|
||||
debugfs_remove_recursive(debugfs_base);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (!debugfs_create_file("enabled_clocks", 0444, debugfs_base, NULL,
|
||||
&enabled_clocks_fops))
|
||||
return -ENOMEM;
|
||||
|
||||
if (!debugfs_create_file("orphan_list", 0444, debugfs_base, NULL,
|
||||
&orphan_list_fops))
|
||||
return -ENOMEM;
|
||||
|
||||
if (!debugfs_create_file("trace_clocks", 0444, debugfs_base, NULL,
|
||||
&trace_clocks_fops))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* clock_debug_register() - Add additional clocks to clock debugfs hierarchy
|
||||
* @list: List of clocks to create debugfs nodes for
|
||||
*/
|
||||
int clock_debug_register(struct clk *clk)
|
||||
{
|
||||
int ret = 0;
|
||||
struct clk *c;
|
||||
|
||||
mutex_lock(&clk_list_lock);
|
||||
if (!list_empty(&clk->list))
|
||||
goto out;
|
||||
|
||||
ret = clock_debug_init();
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (IS_ERR_OR_NULL(measure)) {
|
||||
if (clk->flags & CLKFLAG_MEASURE)
|
||||
measure = clk;
|
||||
if (!IS_ERR_OR_NULL(measure)) {
|
||||
list_for_each_entry(c, &clk_list, list)
|
||||
clock_measure_add(c);
|
||||
}
|
||||
}
|
||||
|
||||
list_add_tail(&clk->list, &clk_list);
|
||||
clock_debug_add(clk);
|
||||
out:
|
||||
mutex_unlock(&clk_list_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print the names of enabled clocks and their parents if debug_suspend is set
|
||||
*/
|
||||
void clock_debug_print_enabled(bool print_parent)
|
||||
{
|
||||
if (likely(!debug_suspend))
|
||||
return;
|
||||
if (print_parent)
|
||||
clock_debug_print_enabled_clocks(NULL);
|
||||
else
|
||||
clock_debug_print_enabled_debug_suspend(NULL);
|
||||
|
||||
}
|
||||
113
drivers/clk/msm/clock-dummy.c
Normal file
113
drivers/clk/msm/clock-dummy.c
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/* Copyright (c) 2011,2013-2014 The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk/msm-clk-provider.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <soc/qcom/msm-clock-controller.h>
|
||||
|
||||
static int dummy_clk_reset(struct clk *clk, enum clk_reset_action action)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy_clk_set_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
clk->rate = rate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy_clk_set_max_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy_clk_set_flags(struct clk *clk, unsigned long flags)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long dummy_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
return clk->rate;
|
||||
}
|
||||
|
||||
static long dummy_clk_round_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
return rate;
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_dummy = {
|
||||
.reset = dummy_clk_reset,
|
||||
.set_rate = dummy_clk_set_rate,
|
||||
.set_max_rate = dummy_clk_set_max_rate,
|
||||
.set_flags = dummy_clk_set_flags,
|
||||
.get_rate = dummy_clk_get_rate,
|
||||
.round_rate = dummy_clk_round_rate,
|
||||
};
|
||||
|
||||
struct clk dummy_clk = {
|
||||
.dbg_name = "dummy_clk",
|
||||
.ops = &clk_ops_dummy,
|
||||
CLK_INIT(dummy_clk),
|
||||
};
|
||||
|
||||
static void *dummy_clk_dt_parser(struct device *dev, struct device_node *np)
|
||||
{
|
||||
struct clk *c;
|
||||
|
||||
c = devm_kzalloc(dev, sizeof(*c), GFP_KERNEL);
|
||||
if (!c)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
c->ops = &clk_ops_dummy;
|
||||
return msmclk_generic_clk_init(dev, np, c);
|
||||
}
|
||||
MSMCLK_PARSER(dummy_clk_dt_parser, "qcom,dummy-clk", 0);
|
||||
|
||||
static struct clk *of_dummy_get(struct of_phandle_args *clkspec,
|
||||
void *data)
|
||||
{
|
||||
return &dummy_clk;
|
||||
}
|
||||
|
||||
static const struct of_device_id msm_clock_dummy_match_table[] = {
|
||||
{ .compatible = "qcom,dummycc" },
|
||||
{}
|
||||
};
|
||||
|
||||
static int msm_clock_dummy_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = of_clk_add_provider(pdev->dev.of_node, of_dummy_get, NULL);
|
||||
if (ret)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_info(&pdev->dev, "Registered DUMMY provider.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver msm_clock_dummy_driver = {
|
||||
.probe = msm_clock_dummy_probe,
|
||||
.driver = {
|
||||
.name = "clock-dummy",
|
||||
.of_match_table = msm_clock_dummy_match_table,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
int __init msm_dummy_clk_init(void)
|
||||
{
|
||||
return platform_driver_register(&msm_clock_dummy_driver);
|
||||
}
|
||||
arch_initcall(msm_dummy_clk_init);
|
||||
921
drivers/clk/msm/clock-generic.c
Normal file
921
drivers/clk/msm/clock-generic.c
Normal file
|
|
@ -0,0 +1,921 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk/msm-clk-provider.h>
|
||||
#include <linux/clk/msm-clock-generic.h>
|
||||
#include <soc/qcom/msm-clock-controller.h>
|
||||
|
||||
/* ==================== Mux clock ==================== */
|
||||
|
||||
static int mux_parent_to_src_sel(struct mux_clk *mux, struct clk *p)
|
||||
{
|
||||
return parent_to_src_sel(mux->parents, mux->num_parents, p);
|
||||
}
|
||||
|
||||
static int mux_set_parent(struct clk *c, struct clk *p)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
int sel = mux_parent_to_src_sel(mux, p);
|
||||
struct clk *old_parent;
|
||||
int rc = 0, i;
|
||||
unsigned long flags;
|
||||
|
||||
if (sel < 0 && mux->rec_parents) {
|
||||
for (i = 0; i < mux->num_rec_parents; i++) {
|
||||
rc = clk_set_parent(mux->rec_parents[i], p);
|
||||
if (!rc) {
|
||||
/*
|
||||
* This is necessary to ensure prepare/enable
|
||||
* counts get propagated correctly.
|
||||
*/
|
||||
p = mux->rec_parents[i];
|
||||
sel = mux_parent_to_src_sel(mux, p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sel < 0)
|
||||
return sel;
|
||||
|
||||
rc = __clk_pre_reparent(c, p, &flags);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rc = mux->ops->set_mux_sel(mux, sel);
|
||||
if (rc)
|
||||
goto set_fail;
|
||||
|
||||
old_parent = c->parent;
|
||||
c->parent = p;
|
||||
c->rate = clk_get_rate(p);
|
||||
__clk_post_reparent(c, old_parent, &flags);
|
||||
|
||||
return 0;
|
||||
|
||||
set_fail:
|
||||
__clk_post_reparent(c, p, &flags);
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long mux_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
int i;
|
||||
unsigned long prate, rrate = 0;
|
||||
|
||||
for (i = 0; i < mux->num_parents; i++) {
|
||||
prate = clk_round_rate(mux->parents[i].src, rate);
|
||||
if (is_better_rate(rate, rrate, prate))
|
||||
rrate = prate;
|
||||
}
|
||||
if (!rrate)
|
||||
return -EINVAL;
|
||||
|
||||
return rrate;
|
||||
}
|
||||
|
||||
static int mux_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
struct clk *new_parent = NULL;
|
||||
int rc = 0, i;
|
||||
unsigned long new_par_curr_rate;
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* Check if one of the possible parents is already at the requested
|
||||
* rate.
|
||||
*/
|
||||
for (i = 0; i < mux->num_parents && mux->try_get_rate; i++) {
|
||||
struct clk *p = mux->parents[i].src;
|
||||
|
||||
if (p->rate == rate && clk_round_rate(p, rate) == rate) {
|
||||
new_parent = mux->parents[i].src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < mux->num_parents && !(!i && new_parent); i++) {
|
||||
if (clk_round_rate(mux->parents[i].src, rate) == rate) {
|
||||
new_parent = mux->parents[i].src;
|
||||
if (!mux->try_new_parent)
|
||||
break;
|
||||
if (mux->try_new_parent && new_parent != c->parent)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_parent == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Switch to safe parent since the old and new parent might be the
|
||||
* same and the parent might temporarily turn off while switching
|
||||
* rates. If the mux can switch between distinct sources safely
|
||||
* (indicated by try_new_parent), and the new source is not the current
|
||||
* parent, do not switch to the safe parent.
|
||||
*/
|
||||
if (mux->safe_sel >= 0 &&
|
||||
!(mux->try_new_parent && (new_parent != c->parent))) {
|
||||
/*
|
||||
* The safe parent might be a clock with multiple sources;
|
||||
* to select the "safe" source, set a safe frequency.
|
||||
*/
|
||||
if (mux->safe_freq) {
|
||||
rc = clk_set_rate(mux->safe_parent, mux->safe_freq);
|
||||
if (rc) {
|
||||
pr_err("Failed to set safe rate on %s\n",
|
||||
clk_name(mux->safe_parent));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Some mux implementations might switch to/from a low power
|
||||
* parent as part of their disable/enable ops. Grab the
|
||||
* enable lock to avoid racing with these implementations.
|
||||
*/
|
||||
spin_lock_irqsave(&c->lock, flags);
|
||||
rc = mux->ops->set_mux_sel(mux, mux->safe_sel);
|
||||
spin_unlock_irqrestore(&c->lock, flags);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
new_par_curr_rate = clk_get_rate(new_parent);
|
||||
rc = clk_set_rate(new_parent, rate);
|
||||
if (rc)
|
||||
goto set_rate_fail;
|
||||
|
||||
rc = mux_set_parent(c, new_parent);
|
||||
if (rc)
|
||||
goto set_par_fail;
|
||||
|
||||
return 0;
|
||||
|
||||
set_par_fail:
|
||||
clk_set_rate(new_parent, new_par_curr_rate);
|
||||
set_rate_fail:
|
||||
WARN(mux->ops->set_mux_sel(mux,
|
||||
mux_parent_to_src_sel(mux, c->parent)),
|
||||
"Set rate failed for %s. Also in bad state!\n", c->dbg_name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mux_enable(struct clk *c)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
|
||||
if (mux->ops->enable)
|
||||
return mux->ops->enable(mux);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mux_disable(struct clk *c)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
|
||||
if (mux->ops->disable)
|
||||
return mux->ops->disable(mux);
|
||||
}
|
||||
|
||||
static struct clk *mux_get_parent(struct clk *c)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
int sel = mux->ops->get_mux_sel(mux);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < mux->num_parents; i++) {
|
||||
if (mux->parents[i].sel == sel)
|
||||
return mux->parents[i].src;
|
||||
}
|
||||
|
||||
/* Unfamiliar parent. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static enum handoff mux_handoff(struct clk *c)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
|
||||
c->rate = clk_get_rate(c->parent);
|
||||
mux->safe_sel = mux_parent_to_src_sel(mux, mux->safe_parent);
|
||||
|
||||
if (mux->en_mask && mux->ops && mux->ops->is_enabled)
|
||||
return mux->ops->is_enabled(mux)
|
||||
? HANDOFF_ENABLED_CLK
|
||||
: HANDOFF_DISABLED_CLK;
|
||||
|
||||
/*
|
||||
* If this function returns 'enabled' even when the clock downstream
|
||||
* of this clock is disabled, then handoff code will unnecessarily
|
||||
* enable the current parent of this clock. If this function always
|
||||
* returns 'disabled' and a clock downstream is on, the clock handoff
|
||||
* code will bump up the ref count for this clock and its current
|
||||
* parent as necessary. So, clocks without an actual HW gate can
|
||||
* always return disabled.
|
||||
*/
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
}
|
||||
|
||||
static void __iomem *mux_clk_list_registers(struct clk *c, int n,
|
||||
struct clk_register_data **regs, u32 *size)
|
||||
{
|
||||
struct mux_clk *mux = to_mux_clk(c);
|
||||
|
||||
if (mux->ops && mux->ops->list_registers)
|
||||
return mux->ops->list_registers(mux, n, regs, size);
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_gen_mux = {
|
||||
.enable = mux_enable,
|
||||
.disable = mux_disable,
|
||||
.set_parent = mux_set_parent,
|
||||
.round_rate = mux_round_rate,
|
||||
.set_rate = mux_set_rate,
|
||||
.handoff = mux_handoff,
|
||||
.get_parent = mux_get_parent,
|
||||
.list_registers = mux_clk_list_registers,
|
||||
};
|
||||
|
||||
/* ==================== Divider clock ==================== */
|
||||
|
||||
static long __div_round_rate(struct div_data *data, unsigned long rate,
|
||||
struct clk *parent, unsigned int *best_div, unsigned long *best_prate)
|
||||
{
|
||||
unsigned int div, min_div, max_div, _best_div = 1;
|
||||
unsigned long prate, _best_prate = 0, rrate = 0, req_prate, actual_rate;
|
||||
unsigned int numer;
|
||||
|
||||
rate = max(rate, 1UL);
|
||||
|
||||
min_div = max(data->min_div, 1U);
|
||||
max_div = min(data->max_div, (unsigned int) (ULONG_MAX));
|
||||
|
||||
/*
|
||||
* div values are doubled for half dividers.
|
||||
* Adjust for that by picking a numer of 2.
|
||||
*/
|
||||
numer = data->is_half_divider ? 2 : 1;
|
||||
|
||||
for (div = min_div; div <= max_div; div++) {
|
||||
if (data->skip_odd_div && (div & 1))
|
||||
if (!(data->allow_div_one && (div == 1)))
|
||||
continue;
|
||||
if (data->skip_even_div && !(div & 1))
|
||||
continue;
|
||||
req_prate = mult_frac(rate, div, numer);
|
||||
prate = clk_round_rate(parent, req_prate);
|
||||
if (IS_ERR_VALUE(prate))
|
||||
break;
|
||||
|
||||
actual_rate = mult_frac(prate, numer, div);
|
||||
if (is_better_rate(rate, rrate, actual_rate)) {
|
||||
rrate = actual_rate;
|
||||
_best_div = div;
|
||||
_best_prate = prate;
|
||||
}
|
||||
|
||||
/*
|
||||
* Trying higher dividers is only going to ask the parent for
|
||||
* a higher rate. If it can't even output a rate higher than
|
||||
* the one we request for this divider, the parent is not
|
||||
* going to be able to output an even higher rate required
|
||||
* for a higher divider. So, stop trying higher dividers.
|
||||
*/
|
||||
if (actual_rate < rate)
|
||||
break;
|
||||
|
||||
if (rrate <= rate + data->rate_margin)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rrate)
|
||||
return -EINVAL;
|
||||
if (best_div)
|
||||
*best_div = _best_div;
|
||||
if (best_prate)
|
||||
*best_prate = _best_prate;
|
||||
|
||||
return rrate;
|
||||
}
|
||||
|
||||
static long div_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
|
||||
return __div_round_rate(&d->data, rate, c->parent, NULL, NULL);
|
||||
}
|
||||
|
||||
static int _find_safe_div(struct clk *c, unsigned long rate)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
struct div_data *data = &d->data;
|
||||
unsigned long fast = max(rate, c->rate);
|
||||
unsigned int numer = data->is_half_divider ? 2 : 1;
|
||||
int i, safe_div = 0;
|
||||
|
||||
if (!d->safe_freq)
|
||||
return 0;
|
||||
|
||||
/* Find the max safe freq that is lesser than fast */
|
||||
for (i = data->max_div; i >= data->min_div; i--)
|
||||
if (mult_frac(d->safe_freq, numer, i) <= fast)
|
||||
safe_div = i;
|
||||
|
||||
return safe_div ?: -EINVAL;
|
||||
}
|
||||
|
||||
static int div_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
int safe_div, div, rc = 0;
|
||||
long rrate, old_prate, new_prate;
|
||||
struct div_data *data = &d->data;
|
||||
|
||||
rrate = __div_round_rate(data, rate, c->parent, &div, &new_prate);
|
||||
if (rrate < rate || rrate > rate + data->rate_margin)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* For fixed divider clock we don't want to return an error if the
|
||||
* requested rate matches the achievable rate. So, don't check for
|
||||
* !d->ops and return an error. __div_round_rate() ensures div ==
|
||||
* d->div if !d->ops.
|
||||
*/
|
||||
|
||||
safe_div = _find_safe_div(c, rate);
|
||||
if (d->safe_freq && safe_div < 0) {
|
||||
pr_err("No safe div on %s for transitioning from %lu to %lu\n",
|
||||
c->dbg_name, c->rate, rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
safe_div = max(safe_div, div);
|
||||
|
||||
if (safe_div > data->div) {
|
||||
rc = d->ops->set_div(d, safe_div);
|
||||
if (rc) {
|
||||
pr_err("Failed to set div %d on %s\n", safe_div,
|
||||
c->dbg_name);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
old_prate = clk_get_rate(c->parent);
|
||||
rc = clk_set_rate(c->parent, new_prate);
|
||||
if (rc)
|
||||
goto set_rate_fail;
|
||||
|
||||
if (div < data->div)
|
||||
rc = d->ops->set_div(d, div);
|
||||
else if (div < safe_div)
|
||||
rc = d->ops->set_div(d, div);
|
||||
if (rc)
|
||||
goto div_dec_fail;
|
||||
|
||||
data->div = div;
|
||||
|
||||
return 0;
|
||||
|
||||
div_dec_fail:
|
||||
WARN(clk_set_rate(c->parent, old_prate),
|
||||
"Set rate failed for %s. Also in bad state!\n", c->dbg_name);
|
||||
set_rate_fail:
|
||||
if (safe_div > data->div)
|
||||
WARN(d->ops->set_div(d, data->div),
|
||||
"Set rate failed for %s. Also in bad state!\n",
|
||||
c->dbg_name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int div_enable(struct clk *c)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
|
||||
if (d->ops && d->ops->enable)
|
||||
return d->ops->enable(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void div_disable(struct clk *c)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
|
||||
if (d->ops && d->ops->disable)
|
||||
return d->ops->disable(d);
|
||||
}
|
||||
|
||||
static enum handoff div_handoff(struct clk *c)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
unsigned int div = d->data.div;
|
||||
|
||||
if (d->ops && d->ops->get_div)
|
||||
div = max(d->ops->get_div(d), 1);
|
||||
div = max(div, 1U);
|
||||
c->rate = clk_get_rate(c->parent) / div;
|
||||
|
||||
if (!d->ops || !d->ops->set_div)
|
||||
d->data.min_div = d->data.max_div = div;
|
||||
d->data.div = div;
|
||||
|
||||
if (d->en_mask && d->ops && d->ops->is_enabled)
|
||||
return d->ops->is_enabled(d)
|
||||
? HANDOFF_ENABLED_CLK
|
||||
: HANDOFF_DISABLED_CLK;
|
||||
|
||||
/*
|
||||
* If this function returns 'enabled' even when the clock downstream
|
||||
* of this clock is disabled, then handoff code will unnecessarily
|
||||
* enable the current parent of this clock. If this function always
|
||||
* returns 'disabled' and a clock downstream is on, the clock handoff
|
||||
* code will bump up the ref count for this clock and its current
|
||||
* parent as necessary. So, clocks without an actual HW gate can
|
||||
* always return disabled.
|
||||
*/
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
}
|
||||
|
||||
static void __iomem *div_clk_list_registers(struct clk *c, int n,
|
||||
struct clk_register_data **regs, u32 *size)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
|
||||
if (d->ops && d->ops->list_registers)
|
||||
return d->ops->list_registers(d, n, regs, size);
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_div = {
|
||||
.enable = div_enable,
|
||||
.disable = div_disable,
|
||||
.round_rate = div_round_rate,
|
||||
.set_rate = div_set_rate,
|
||||
.handoff = div_handoff,
|
||||
.list_registers = div_clk_list_registers,
|
||||
};
|
||||
|
||||
static long __slave_div_round_rate(struct clk *c, unsigned long rate,
|
||||
int *best_div)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
unsigned int div, min_div, max_div;
|
||||
long p_rate;
|
||||
|
||||
rate = max(rate, 1UL);
|
||||
|
||||
min_div = d->data.min_div;
|
||||
max_div = d->data.max_div;
|
||||
|
||||
p_rate = clk_get_rate(c->parent);
|
||||
div = DIV_ROUND_CLOSEST(p_rate, rate);
|
||||
div = max(div, min_div);
|
||||
div = min(div, max_div);
|
||||
if (best_div)
|
||||
*best_div = div;
|
||||
|
||||
return p_rate / div;
|
||||
}
|
||||
|
||||
static long slave_div_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
return __slave_div_round_rate(c, rate, NULL);
|
||||
}
|
||||
|
||||
static int slave_div_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
int div, rc = 0;
|
||||
long rrate;
|
||||
|
||||
rrate = __slave_div_round_rate(c, rate, &div);
|
||||
if (rrate != rate)
|
||||
return -EINVAL;
|
||||
|
||||
if (div == d->data.div)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* For fixed divider clock we don't want to return an error if the
|
||||
* requested rate matches the achievable rate. So, don't check for
|
||||
* !d->ops and return an error. __slave_div_round_rate() ensures
|
||||
* div == d->data.div if !d->ops.
|
||||
*/
|
||||
rc = d->ops->set_div(d, div);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
d->data.div = div;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long slave_div_get_rate(struct clk *c)
|
||||
{
|
||||
struct div_clk *d = to_div_clk(c);
|
||||
|
||||
if (!d->data.div)
|
||||
return 0;
|
||||
return clk_get_rate(c->parent) / d->data.div;
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_slave_div = {
|
||||
.enable = div_enable,
|
||||
.disable = div_disable,
|
||||
.round_rate = slave_div_round_rate,
|
||||
.set_rate = slave_div_set_rate,
|
||||
.get_rate = slave_div_get_rate,
|
||||
.handoff = div_handoff,
|
||||
.list_registers = div_clk_list_registers,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* External clock
|
||||
* Some clock controllers have input clock signal that come from outside the
|
||||
* clock controller. That input clock signal might then be used as a source for
|
||||
* several clocks inside the clock controller. This external clock
|
||||
* implementation models this input clock signal by just passing on the requests
|
||||
* to the clock's parent, the original external clock source. The driver for the
|
||||
* clock controller should clk_get() the original external clock in the probe
|
||||
* function and set is as a parent to this external clock..
|
||||
*/
|
||||
|
||||
long parent_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
return clk_round_rate(c->parent, rate);
|
||||
}
|
||||
|
||||
int parent_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
return clk_set_rate(c->parent, rate);
|
||||
}
|
||||
|
||||
unsigned long parent_get_rate(struct clk *c)
|
||||
{
|
||||
return clk_get_rate(c->parent);
|
||||
}
|
||||
|
||||
static int ext_set_parent(struct clk *c, struct clk *p)
|
||||
{
|
||||
return clk_set_parent(c->parent, p);
|
||||
}
|
||||
|
||||
static struct clk *ext_get_parent(struct clk *c)
|
||||
{
|
||||
struct ext_clk *ext = to_ext_clk(c);
|
||||
|
||||
if (!IS_ERR_OR_NULL(c->parent))
|
||||
return c->parent;
|
||||
return clk_get(ext->dev, ext->clk_id);
|
||||
}
|
||||
|
||||
static enum handoff ext_handoff(struct clk *c)
|
||||
{
|
||||
c->rate = clk_get_rate(c->parent);
|
||||
/* Similar reasoning applied in div_handoff, see comment there. */
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_ext = {
|
||||
.handoff = ext_handoff,
|
||||
.round_rate = parent_round_rate,
|
||||
.set_rate = parent_set_rate,
|
||||
.get_rate = parent_get_rate,
|
||||
.set_parent = ext_set_parent,
|
||||
.get_parent = ext_get_parent,
|
||||
};
|
||||
|
||||
static void *ext_clk_dt_parser(struct device *dev, struct device_node *np)
|
||||
{
|
||||
struct ext_clk *ext;
|
||||
const char *str;
|
||||
int rc;
|
||||
|
||||
ext = devm_kzalloc(dev, sizeof(*ext), GFP_KERNEL);
|
||||
if (!ext)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ext->dev = dev;
|
||||
rc = of_property_read_string(np, "qcom,clock-names", &str);
|
||||
if (!rc)
|
||||
ext->clk_id = (void *)str;
|
||||
|
||||
ext->c.ops = &clk_ops_ext;
|
||||
return msmclk_generic_clk_init(dev, np, &ext->c);
|
||||
}
|
||||
MSMCLK_PARSER(ext_clk_dt_parser, "qcom,ext-clk", 0);
|
||||
|
||||
/* ==================== Mux_div clock ==================== */
|
||||
|
||||
static int mux_div_clk_enable(struct clk *c)
|
||||
{
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
|
||||
if (md->ops->enable)
|
||||
return md->ops->enable(md);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mux_div_clk_disable(struct clk *c)
|
||||
{
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
|
||||
if (md->ops->disable)
|
||||
return md->ops->disable(md);
|
||||
}
|
||||
|
||||
static long __mux_div_round_rate(struct clk *c, unsigned long rate,
|
||||
struct clk **best_parent, int *best_div, unsigned long *best_prate)
|
||||
{
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
unsigned int i;
|
||||
unsigned long rrate, best = 0, _best_div = 0, _best_prate = 0;
|
||||
struct clk *_best_parent = 0;
|
||||
|
||||
if (md->try_get_rate) {
|
||||
for (i = 0; i < md->num_parents; i++) {
|
||||
int divider;
|
||||
unsigned long p_rate;
|
||||
|
||||
rrate = __div_round_rate(&md->data, rate,
|
||||
md->parents[i].src,
|
||||
÷r, &p_rate);
|
||||
/*
|
||||
* Check if one of the possible parents is already at
|
||||
* the requested rate.
|
||||
*/
|
||||
if (p_rate == clk_get_rate(md->parents[i].src)
|
||||
&& rrate == rate) {
|
||||
best = rrate;
|
||||
_best_div = divider;
|
||||
_best_prate = p_rate;
|
||||
_best_parent = md->parents[i].src;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < md->num_parents; i++) {
|
||||
int div;
|
||||
unsigned long prate;
|
||||
|
||||
rrate = __div_round_rate(&md->data, rate, md->parents[i].src,
|
||||
&div, &prate);
|
||||
|
||||
if (is_better_rate(rate, best, rrate)) {
|
||||
best = rrate;
|
||||
_best_div = div;
|
||||
_best_prate = prate;
|
||||
_best_parent = md->parents[i].src;
|
||||
}
|
||||
|
||||
if (rate <= rrate && rrate <= rate + md->data.rate_margin)
|
||||
break;
|
||||
}
|
||||
end:
|
||||
if (best_div)
|
||||
*best_div = _best_div;
|
||||
if (best_prate)
|
||||
*best_prate = _best_prate;
|
||||
if (best_parent)
|
||||
*best_parent = _best_parent;
|
||||
|
||||
if (best)
|
||||
return best;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static long mux_div_clk_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
return __mux_div_round_rate(c, rate, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
/* requires enable lock to be held */
|
||||
static int __set_src_div(struct mux_div_clk *md, struct clk *parent, u32 div)
|
||||
{
|
||||
u32 rc = 0, src_sel;
|
||||
|
||||
src_sel = parent_to_src_sel(md->parents, md->num_parents, parent);
|
||||
/*
|
||||
* If the clock is disabled, don't change to the new settings until
|
||||
* the clock is reenabled
|
||||
*/
|
||||
if (md->c.count)
|
||||
rc = md->ops->set_src_div(md, src_sel, div);
|
||||
if (!rc) {
|
||||
md->data.div = div;
|
||||
md->src_sel = src_sel;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int set_src_div(struct mux_div_clk *md, struct clk *parent, u32 div)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 rc;
|
||||
|
||||
spin_lock_irqsave(&md->c.lock, flags);
|
||||
rc = __set_src_div(md, parent, div);
|
||||
spin_unlock_irqrestore(&md->c.lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Must be called after handoff to ensure parent clock rates are initialized */
|
||||
static int safe_parent_init_once(struct clk *c)
|
||||
{
|
||||
unsigned long rrate;
|
||||
u32 best_div;
|
||||
struct clk *best_parent;
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
|
||||
if (IS_ERR(md->safe_parent))
|
||||
return -EINVAL;
|
||||
if (!md->safe_freq || md->safe_parent)
|
||||
return 0;
|
||||
|
||||
rrate = __mux_div_round_rate(c, md->safe_freq, &best_parent,
|
||||
&best_div, NULL);
|
||||
|
||||
if (rrate == md->safe_freq) {
|
||||
md->safe_div = best_div;
|
||||
md->safe_parent = best_parent;
|
||||
} else {
|
||||
md->safe_parent = ERR_PTR(-EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mux_div_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
unsigned long flags, rrate;
|
||||
unsigned long new_prate, new_parent_orig_rate;
|
||||
struct clk *old_parent, *new_parent;
|
||||
u32 new_div, old_div;
|
||||
int rc;
|
||||
|
||||
rc = safe_parent_init_once(c);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rrate = __mux_div_round_rate(c, rate, &new_parent, &new_div,
|
||||
&new_prate);
|
||||
if (rrate < rate || rrate > rate + md->data.rate_margin)
|
||||
return -EINVAL;
|
||||
|
||||
old_parent = c->parent;
|
||||
old_div = md->data.div;
|
||||
|
||||
/* Refer to the description of safe_freq in clock-generic.h */
|
||||
if (md->safe_freq)
|
||||
rc = set_src_div(md, md->safe_parent, md->safe_div);
|
||||
|
||||
else if (new_parent == old_parent && new_div >= old_div) {
|
||||
/*
|
||||
* If both the parent_rate and divider changes, there may be an
|
||||
* intermediate frequency generated. Ensure this intermediate
|
||||
* frequency is less than both the new rate and previous rate.
|
||||
*/
|
||||
rc = set_src_div(md, old_parent, new_div);
|
||||
}
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
new_parent_orig_rate = clk_get_rate(new_parent);
|
||||
rc = clk_set_rate(new_parent, new_prate);
|
||||
if (rc) {
|
||||
pr_err("failed to set %s to %ld\n",
|
||||
clk_name(new_parent), new_prate);
|
||||
goto err_set_rate;
|
||||
}
|
||||
|
||||
rc = __clk_pre_reparent(c, new_parent, &flags);
|
||||
if (rc)
|
||||
goto err_pre_reparent;
|
||||
|
||||
/* Set divider and mux src atomically */
|
||||
rc = __set_src_div(md, new_parent, new_div);
|
||||
if (rc)
|
||||
goto err_set_src_div;
|
||||
|
||||
c->parent = new_parent;
|
||||
|
||||
__clk_post_reparent(c, old_parent, &flags);
|
||||
return 0;
|
||||
|
||||
err_set_src_div:
|
||||
/* Not switching to new_parent, so disable it */
|
||||
__clk_post_reparent(c, new_parent, &flags);
|
||||
err_pre_reparent:
|
||||
rc = clk_set_rate(new_parent, new_parent_orig_rate);
|
||||
WARN(rc, "%s: error changing new_parent (%s) rate back to %ld\n",
|
||||
clk_name(c), clk_name(new_parent), new_parent_orig_rate);
|
||||
err_set_rate:
|
||||
rc = set_src_div(md, old_parent, old_div);
|
||||
WARN(rc, "%s: error changing back to original div (%d) and parent (%s)\n",
|
||||
clk_name(c), old_div, clk_name(old_parent));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct clk *mux_div_clk_get_parent(struct clk *c)
|
||||
{
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
u32 i, div, src_sel;
|
||||
|
||||
md->ops->get_src_div(md, &src_sel, &div);
|
||||
|
||||
md->data.div = div;
|
||||
md->src_sel = src_sel;
|
||||
|
||||
for (i = 0; i < md->num_parents; i++) {
|
||||
if (md->parents[i].sel == src_sel)
|
||||
return md->parents[i].src;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static enum handoff mux_div_clk_handoff(struct clk *c)
|
||||
{
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
unsigned long parent_rate;
|
||||
unsigned int numer;
|
||||
|
||||
parent_rate = clk_get_rate(c->parent);
|
||||
/*
|
||||
* div values are doubled for half dividers.
|
||||
* Adjust for that by picking a numer of 2.
|
||||
*/
|
||||
numer = md->data.is_half_divider ? 2 : 1;
|
||||
|
||||
if (md->data.div) {
|
||||
c->rate = mult_frac(parent_rate, numer, md->data.div);
|
||||
} else {
|
||||
c->rate = 0;
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
}
|
||||
|
||||
if (md->en_mask && md->ops && md->ops->is_enabled)
|
||||
return md->ops->is_enabled(md)
|
||||
? HANDOFF_ENABLED_CLK
|
||||
: HANDOFF_DISABLED_CLK;
|
||||
|
||||
/*
|
||||
* If this function returns 'enabled' even when the clock downstream
|
||||
* of this clock is disabled, then handoff code will unnecessarily
|
||||
* enable the current parent of this clock. If this function always
|
||||
* returns 'disabled' and a clock downstream is on, the clock handoff
|
||||
* code will bump up the ref count for this clock and its current
|
||||
* parent as necessary. So, clocks without an actual HW gate can
|
||||
* always return disabled.
|
||||
*/
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
}
|
||||
|
||||
static void __iomem *mux_div_clk_list_registers(struct clk *c, int n,
|
||||
struct clk_register_data **regs, u32 *size)
|
||||
{
|
||||
struct mux_div_clk *md = to_mux_div_clk(c);
|
||||
|
||||
if (md->ops && md->ops->list_registers)
|
||||
return md->ops->list_registers(md, n, regs, size);
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_mux_div_clk = {
|
||||
.enable = mux_div_clk_enable,
|
||||
.disable = mux_div_clk_disable,
|
||||
.set_rate = mux_div_clk_set_rate,
|
||||
.round_rate = mux_div_clk_round_rate,
|
||||
.get_parent = mux_div_clk_get_parent,
|
||||
.handoff = mux_div_clk_handoff,
|
||||
.list_registers = mux_div_clk_list_registers,
|
||||
};
|
||||
2907
drivers/clk/msm/clock-local2.c
Normal file
2907
drivers/clk/msm/clock-local2.c
Normal file
File diff suppressed because it is too large
Load diff
1204
drivers/clk/msm/clock-pll.c
Normal file
1204
drivers/clk/msm/clock-pll.c
Normal file
File diff suppressed because it is too large
Load diff
473
drivers/clk/msm/clock-rpm.c
Normal file
473
drivers/clk/msm/clock-rpm.c
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
/* Copyright (c) 2010-2015, 2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/rtmutex.h>
|
||||
#include <linux/clk/msm-clk-provider.h>
|
||||
#include <soc/qcom/clock-rpm.h>
|
||||
#include <soc/qcom/msm-clock-controller.h>
|
||||
|
||||
#define __clk_rpmrs_set_rate(r, value, ctx) \
|
||||
((r)->rpmrs_data->set_rate_fn((r), (value), (ctx)))
|
||||
|
||||
#define clk_rpmrs_set_rate_sleep(r, value) \
|
||||
__clk_rpmrs_set_rate((r), (value), (r)->rpmrs_data->ctx_sleep_id)
|
||||
|
||||
#define clk_rpmrs_set_rate_active(r, value) \
|
||||
__clk_rpmrs_set_rate((r), (value), (r)->rpmrs_data->ctx_active_id)
|
||||
|
||||
static int clk_rpmrs_set_rate_smd(struct rpm_clk *r, uint32_t value,
|
||||
uint32_t context)
|
||||
{
|
||||
int ret;
|
||||
|
||||
struct msm_rpm_kvp kvp = {
|
||||
.key = r->rpm_key,
|
||||
.data = (void *)&value,
|
||||
.length = sizeof(value),
|
||||
};
|
||||
|
||||
switch (context) {
|
||||
case MSM_RPM_CTX_ACTIVE_SET:
|
||||
if (*r->last_active_set_vote == value)
|
||||
return 0;
|
||||
break;
|
||||
case MSM_RPM_CTX_SLEEP_SET:
|
||||
if (*r->last_sleep_set_vote == value)
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
};
|
||||
|
||||
ret = msm_rpm_send_message(context, r->rpm_res_type, r->rpm_clk_id,
|
||||
&kvp, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (context) {
|
||||
case MSM_RPM_CTX_ACTIVE_SET:
|
||||
*r->last_active_set_vote = value;
|
||||
break;
|
||||
case MSM_RPM_CTX_SLEEP_SET:
|
||||
*r->last_sleep_set_vote = value;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_rpmrs_handoff_smd(struct rpm_clk *r)
|
||||
{
|
||||
if (!r->branch)
|
||||
r->c.rate = INT_MAX;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_rpmrs_is_enabled_smd(struct rpm_clk *r)
|
||||
{
|
||||
return !!r->c.prepare_count;
|
||||
}
|
||||
|
||||
struct clk_rpmrs_data {
|
||||
int (*set_rate_fn)(struct rpm_clk *r, uint32_t value, uint32_t context);
|
||||
int (*get_rate_fn)(struct rpm_clk *r);
|
||||
int (*handoff_fn)(struct rpm_clk *r);
|
||||
int (*is_enabled)(struct rpm_clk *r);
|
||||
int ctx_active_id;
|
||||
int ctx_sleep_id;
|
||||
};
|
||||
|
||||
struct clk_rpmrs_data clk_rpmrs_data_smd = {
|
||||
.set_rate_fn = clk_rpmrs_set_rate_smd,
|
||||
.handoff_fn = clk_rpmrs_handoff_smd,
|
||||
.is_enabled = clk_rpmrs_is_enabled_smd,
|
||||
.ctx_active_id = MSM_RPM_CTX_ACTIVE_SET,
|
||||
.ctx_sleep_id = MSM_RPM_CTX_SLEEP_SET,
|
||||
};
|
||||
|
||||
static DEFINE_RT_MUTEX(rpm_clock_lock);
|
||||
|
||||
static void to_active_sleep_khz(struct rpm_clk *r, unsigned long rate,
|
||||
unsigned long *active_khz, unsigned long *sleep_khz)
|
||||
{
|
||||
/* Convert the rate (hz) to khz */
|
||||
*active_khz = DIV_ROUND_UP(rate, 1000);
|
||||
|
||||
/*
|
||||
* Active-only clocks don't care what the rate is during sleep. So,
|
||||
* they vote for zero.
|
||||
*/
|
||||
if (r->active_only)
|
||||
*sleep_khz = 0;
|
||||
else
|
||||
*sleep_khz = *active_khz;
|
||||
}
|
||||
|
||||
static int rpm_clk_prepare(struct clk *clk)
|
||||
{
|
||||
struct rpm_clk *r = to_rpm_clk(clk);
|
||||
uint32_t value;
|
||||
int rc = 0;
|
||||
unsigned long this_khz, this_sleep_khz;
|
||||
unsigned long peer_khz = 0, peer_sleep_khz = 0;
|
||||
struct rpm_clk *peer = r->peer;
|
||||
|
||||
rt_mutex_lock(&rpm_clock_lock);
|
||||
|
||||
to_active_sleep_khz(r, r->c.rate, &this_khz, &this_sleep_khz);
|
||||
|
||||
/* Don't send requests to the RPM if the rate has not been set. */
|
||||
if (this_khz == 0)
|
||||
goto out;
|
||||
|
||||
/* Take peer clock's rate into account only if it's enabled. */
|
||||
if (peer->enabled)
|
||||
to_active_sleep_khz(peer, peer->c.rate,
|
||||
&peer_khz, &peer_sleep_khz);
|
||||
|
||||
value = max(this_khz, peer_khz);
|
||||
if (r->branch)
|
||||
value = !!value;
|
||||
|
||||
rc = clk_rpmrs_set_rate_active(r, value);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
value = max(this_sleep_khz, peer_sleep_khz);
|
||||
if (r->branch)
|
||||
value = !!value;
|
||||
|
||||
rc = clk_rpmrs_set_rate_sleep(r, value);
|
||||
if (rc) {
|
||||
/* Undo the active set vote and restore it to peer_khz */
|
||||
value = peer_khz;
|
||||
rc = clk_rpmrs_set_rate_active(r, value);
|
||||
}
|
||||
|
||||
out:
|
||||
if (!rc)
|
||||
r->enabled = true;
|
||||
|
||||
rt_mutex_unlock(&rpm_clock_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void rpm_clk_unprepare(struct clk *clk)
|
||||
{
|
||||
struct rpm_clk *r = to_rpm_clk(clk);
|
||||
|
||||
rt_mutex_lock(&rpm_clock_lock);
|
||||
|
||||
if (r->c.rate) {
|
||||
uint32_t value;
|
||||
struct rpm_clk *peer = r->peer;
|
||||
unsigned long peer_khz = 0, peer_sleep_khz = 0;
|
||||
int rc;
|
||||
|
||||
/* Take peer clock's rate into account only if it's enabled. */
|
||||
if (peer->enabled)
|
||||
to_active_sleep_khz(peer, peer->c.rate,
|
||||
&peer_khz, &peer_sleep_khz);
|
||||
|
||||
value = r->branch ? !!peer_khz : peer_khz;
|
||||
rc = clk_rpmrs_set_rate_active(r, value);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
value = r->branch ? !!peer_sleep_khz : peer_sleep_khz;
|
||||
rc = clk_rpmrs_set_rate_sleep(r, value);
|
||||
}
|
||||
r->enabled = false;
|
||||
out:
|
||||
rt_mutex_unlock(&rpm_clock_lock);
|
||||
|
||||
}
|
||||
|
||||
static int rpm_clk_set_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
struct rpm_clk *r = to_rpm_clk(clk);
|
||||
unsigned long this_khz, this_sleep_khz;
|
||||
int rc = 0;
|
||||
|
||||
rt_mutex_lock(&rpm_clock_lock);
|
||||
|
||||
if (r->enabled) {
|
||||
uint32_t value;
|
||||
struct rpm_clk *peer = r->peer;
|
||||
unsigned long peer_khz = 0, peer_sleep_khz = 0;
|
||||
|
||||
to_active_sleep_khz(r, rate, &this_khz, &this_sleep_khz);
|
||||
|
||||
/* Take peer clock's rate into account only if it's enabled. */
|
||||
if (peer->enabled)
|
||||
to_active_sleep_khz(peer, peer->c.rate,
|
||||
&peer_khz, &peer_sleep_khz);
|
||||
|
||||
value = max(this_khz, peer_khz);
|
||||
rc = clk_rpmrs_set_rate_active(r, value);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
value = max(this_sleep_khz, peer_sleep_khz);
|
||||
rc = clk_rpmrs_set_rate_sleep(r, value);
|
||||
}
|
||||
|
||||
out:
|
||||
rt_mutex_unlock(&rpm_clock_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static unsigned long rpm_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rpm_clk *r = to_rpm_clk(clk);
|
||||
|
||||
if (r->rpmrs_data->get_rate_fn)
|
||||
return r->rpmrs_data->get_rate_fn(r);
|
||||
else
|
||||
return clk->rate;
|
||||
}
|
||||
|
||||
static int rpm_clk_is_enabled(struct clk *clk)
|
||||
{
|
||||
struct rpm_clk *r = to_rpm_clk(clk);
|
||||
|
||||
return r->rpmrs_data->is_enabled(r);
|
||||
}
|
||||
|
||||
static long rpm_clk_round_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
/* Not supported. */
|
||||
return rate;
|
||||
}
|
||||
|
||||
static bool rpm_clk_is_local(struct clk *clk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum handoff rpm_clk_handoff(struct clk *clk)
|
||||
{
|
||||
struct rpm_clk *r = to_rpm_clk(clk);
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Querying an RPM clock's status will return 0 unless the clock's
|
||||
* rate has previously been set through the RPM. When handing off,
|
||||
* assume these clocks are enabled (unless the RPM call fails) so
|
||||
* child clocks of these RPM clocks can still be handed off.
|
||||
*/
|
||||
rc = r->rpmrs_data->handoff_fn(r);
|
||||
if (rc < 0)
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
|
||||
/*
|
||||
* Since RPM handoff code may update the software rate of the clock by
|
||||
* querying the RPM, we need to make sure our request to RPM now
|
||||
* matches the software rate of the clock. When we send the request
|
||||
* to RPM, we also need to update any other state info we would
|
||||
* normally update. So, call the appropriate clock function instead
|
||||
* of directly using the RPM driver APIs.
|
||||
*/
|
||||
rc = rpm_clk_prepare(clk);
|
||||
if (rc < 0)
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
|
||||
return HANDOFF_ENABLED_CLK;
|
||||
}
|
||||
|
||||
#define RPM_MISC_CLK_TYPE 0x306b6c63
|
||||
#define RPM_SCALING_ENABLE_ID 0x2
|
||||
|
||||
int enable_rpm_scaling(void)
|
||||
{
|
||||
int rc, value = 0x1;
|
||||
static int is_inited;
|
||||
|
||||
struct msm_rpm_kvp kvp = {
|
||||
.key = RPM_SMD_KEY_ENABLE,
|
||||
.data = (void *)&value,
|
||||
.length = sizeof(value),
|
||||
};
|
||||
|
||||
if (is_inited)
|
||||
return 0;
|
||||
|
||||
rc = msm_rpm_send_message_noirq(MSM_RPM_CTX_SLEEP_SET,
|
||||
RPM_MISC_CLK_TYPE, RPM_SCALING_ENABLE_ID, &kvp, 1);
|
||||
if (rc < 0) {
|
||||
if (rc != -EPROBE_DEFER)
|
||||
WARN(1, "RPM clock scaling (sleep set) did not enable!\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = msm_rpm_send_message_noirq(MSM_RPM_CTX_ACTIVE_SET,
|
||||
RPM_MISC_CLK_TYPE, RPM_SCALING_ENABLE_ID, &kvp, 1);
|
||||
if (rc < 0) {
|
||||
if (rc != -EPROBE_DEFER)
|
||||
WARN(1, "RPM clock scaling (active set) did not enable!\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
is_inited++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vote_bimc(struct rpm_clk *r, uint32_t value)
|
||||
{
|
||||
int rc;
|
||||
|
||||
struct msm_rpm_kvp kvp = {
|
||||
.key = r->rpm_key,
|
||||
.data = (void *)&value,
|
||||
.length = sizeof(value),
|
||||
};
|
||||
|
||||
rc = msm_rpm_send_message_noirq(MSM_RPM_CTX_ACTIVE_SET,
|
||||
r->rpm_res_type, r->rpmrs_data->ctx_active_id,
|
||||
&kvp, 1);
|
||||
if (rc < 0) {
|
||||
if (rc != -EPROBE_DEFER)
|
||||
WARN(1, "BIMC vote not sent!\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_rpm = {
|
||||
.prepare = rpm_clk_prepare,
|
||||
.unprepare = rpm_clk_unprepare,
|
||||
.set_rate = rpm_clk_set_rate,
|
||||
.get_rate = rpm_clk_get_rate,
|
||||
.is_enabled = rpm_clk_is_enabled,
|
||||
.round_rate = rpm_clk_round_rate,
|
||||
.is_local = rpm_clk_is_local,
|
||||
.handoff = rpm_clk_handoff,
|
||||
};
|
||||
|
||||
const struct clk_ops clk_ops_rpm_branch = {
|
||||
.prepare = rpm_clk_prepare,
|
||||
.unprepare = rpm_clk_unprepare,
|
||||
.is_local = rpm_clk_is_local,
|
||||
.handoff = rpm_clk_handoff,
|
||||
};
|
||||
|
||||
static struct rpm_clk *rpm_clk_dt_parser_common(struct device *dev,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct rpm_clk *rpm, *peer;
|
||||
struct clk *c;
|
||||
int rc = 0;
|
||||
phandle p;
|
||||
const char *str;
|
||||
|
||||
rpm = devm_kzalloc(dev, sizeof(*rpm), GFP_KERNEL);
|
||||
if (!rpm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
rc = of_property_read_phandle_index(np, "qcom,rpm-peer", 0, &p);
|
||||
if (rc) {
|
||||
dt_err(np, "missing qcom,rpm-peer dt property\n");
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
/* Rely on whoever's called last to setup the circular ref */
|
||||
c = msmclk_lookup_phandle(dev, p);
|
||||
if (!IS_ERR(c)) {
|
||||
uint32_t *sleep = devm_kzalloc(dev, sizeof(uint32_t),
|
||||
GFP_KERNEL);
|
||||
uint32_t *active =
|
||||
devm_kzalloc(dev, sizeof(uint32_t),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!sleep || !active)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
peer = to_rpm_clk(c);
|
||||
peer->peer = rpm;
|
||||
rpm->peer = peer;
|
||||
rpm->last_active_set_vote = active;
|
||||
peer->last_active_set_vote = active;
|
||||
rpm->last_sleep_set_vote = sleep;
|
||||
peer->last_sleep_set_vote = sleep;
|
||||
}
|
||||
|
||||
rpm->rpmrs_data = &clk_rpmrs_data_smd;
|
||||
rpm->active_only = of_device_is_compatible(np, "qcom,rpm-a-clk") ||
|
||||
of_device_is_compatible(np, "qcom,rpm-branch-a-clk");
|
||||
|
||||
rc = of_property_read_string(np, "qcom,res-type", &str);
|
||||
if (rc) {
|
||||
dt_err(np, "missing qcom,res-type dt property\n");
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
if (sscanf(str, "%4c", (char *) &rpm->rpm_res_type) <= 0)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
rc = of_property_read_u32(np, "qcom,res-id", &rpm->rpm_clk_id);
|
||||
if (rc) {
|
||||
dt_err(np, "missing qcom,res-id dt property\n");
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
rc = of_property_read_string(np, "qcom,key", &str);
|
||||
if (rc) {
|
||||
dt_err(np, "missing qcom,key dt property\n");
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
if (sscanf(str, "%4c", (char *) &rpm->rpm_key) <= 0)
|
||||
return ERR_PTR(-EINVAL);
|
||||
return rpm;
|
||||
}
|
||||
|
||||
static void *rpm_clk_dt_parser(struct device *dev, struct device_node *np)
|
||||
{
|
||||
struct rpm_clk *rpm;
|
||||
|
||||
rpm = rpm_clk_dt_parser_common(dev, np);
|
||||
if (IS_ERR(rpm))
|
||||
return rpm;
|
||||
|
||||
rpm->c.ops = &clk_ops_rpm;
|
||||
return msmclk_generic_clk_init(dev, np, &rpm->c);
|
||||
}
|
||||
|
||||
static void *rpm_branch_clk_dt_parser(struct device *dev,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct rpm_clk *rpm;
|
||||
u32 rate;
|
||||
int rc;
|
||||
|
||||
rpm = rpm_clk_dt_parser_common(dev, np);
|
||||
if (IS_ERR(rpm))
|
||||
return rpm;
|
||||
|
||||
rpm->c.ops = &clk_ops_rpm_branch;
|
||||
rpm->branch = true;
|
||||
|
||||
rc = of_property_read_u32(np, "qcom,rcg-init-rate", &rate);
|
||||
if (!rc)
|
||||
rpm->c.rate = rate;
|
||||
|
||||
return msmclk_generic_clk_init(dev, np, &rpm->c);
|
||||
}
|
||||
MSMCLK_PARSER(rpm_clk_dt_parser, "qcom,rpm-clk", 0);
|
||||
MSMCLK_PARSER(rpm_clk_dt_parser, "qcom,rpm-a-clk", 1);
|
||||
MSMCLK_PARSER(rpm_branch_clk_dt_parser, "qcom,rpm-branch-clk", 0);
|
||||
MSMCLK_PARSER(rpm_branch_clk_dt_parser, "qcom,rpm-branch-a-clk", 1);
|
||||
202
drivers/clk/msm/clock-voter.c
Normal file
202
drivers/clk/msm/clock-voter.c
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/* Copyright (c) 2010-2015, 2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/rtmutex.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk/msm-clk-provider.h>
|
||||
#include <soc/qcom/clock-voter.h>
|
||||
#include <soc/qcom/msm-clock-controller.h>
|
||||
|
||||
static DEFINE_RT_MUTEX(voter_clk_lock);
|
||||
|
||||
/* Aggregate the rate of clocks that are currently on. */
|
||||
static unsigned long voter_clk_aggregate_rate(const struct clk *parent)
|
||||
{
|
||||
struct clk *clk;
|
||||
unsigned long rate = 0;
|
||||
|
||||
list_for_each_entry(clk, &parent->children, siblings) {
|
||||
struct clk_voter *v = to_clk_voter(clk);
|
||||
|
||||
if (v->enabled)
|
||||
rate = max(clk->rate, rate);
|
||||
}
|
||||
return rate;
|
||||
}
|
||||
|
||||
static int voter_clk_set_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
int ret = 0;
|
||||
struct clk *clkp;
|
||||
struct clk_voter *clkh, *v = to_clk_voter(clk);
|
||||
unsigned long cur_rate, new_rate, other_rate = 0;
|
||||
|
||||
if (v->is_branch)
|
||||
return 0;
|
||||
|
||||
rt_mutex_lock(&voter_clk_lock);
|
||||
|
||||
if (v->enabled) {
|
||||
struct clk *parent = clk->parent;
|
||||
|
||||
/*
|
||||
* Get the aggregate rate without this clock's vote and update
|
||||
* if the new rate is different than the current rate
|
||||
*/
|
||||
list_for_each_entry(clkp, &parent->children, siblings) {
|
||||
clkh = to_clk_voter(clkp);
|
||||
if (clkh->enabled && clkh != v)
|
||||
other_rate = max(clkp->rate, other_rate);
|
||||
}
|
||||
|
||||
cur_rate = max(other_rate, clk->rate);
|
||||
new_rate = max(other_rate, rate);
|
||||
|
||||
if (new_rate != cur_rate) {
|
||||
ret = clk_set_rate(parent, new_rate);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
clk->rate = rate;
|
||||
unlock:
|
||||
rt_mutex_unlock(&voter_clk_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int voter_clk_prepare(struct clk *clk)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long cur_rate;
|
||||
struct clk *parent;
|
||||
struct clk_voter *v = to_clk_voter(clk);
|
||||
|
||||
rt_mutex_lock(&voter_clk_lock);
|
||||
parent = clk->parent;
|
||||
|
||||
if (v->is_branch) {
|
||||
v->enabled = true;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Increase the rate if this clock is voting for a higher rate
|
||||
* than the current rate.
|
||||
*/
|
||||
cur_rate = voter_clk_aggregate_rate(parent);
|
||||
if (clk->rate > cur_rate) {
|
||||
ret = clk_set_rate(parent, clk->rate);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
v->enabled = true;
|
||||
out:
|
||||
rt_mutex_unlock(&voter_clk_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void voter_clk_unprepare(struct clk *clk)
|
||||
{
|
||||
unsigned long cur_rate, new_rate;
|
||||
struct clk *parent;
|
||||
struct clk_voter *v = to_clk_voter(clk);
|
||||
|
||||
|
||||
rt_mutex_lock(&voter_clk_lock);
|
||||
parent = clk->parent;
|
||||
|
||||
/*
|
||||
* Decrease the rate if this clock was the only one voting for
|
||||
* the highest rate.
|
||||
*/
|
||||
v->enabled = false;
|
||||
if (v->is_branch)
|
||||
goto out;
|
||||
|
||||
new_rate = voter_clk_aggregate_rate(parent);
|
||||
cur_rate = max(new_rate, clk->rate);
|
||||
|
||||
if (new_rate < cur_rate)
|
||||
clk_set_rate(parent, new_rate);
|
||||
|
||||
out:
|
||||
rt_mutex_unlock(&voter_clk_lock);
|
||||
}
|
||||
|
||||
static int voter_clk_is_enabled(struct clk *clk)
|
||||
{
|
||||
struct clk_voter *v = to_clk_voter(clk);
|
||||
|
||||
return v->enabled;
|
||||
}
|
||||
|
||||
static long voter_clk_round_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
return clk_round_rate(clk->parent, rate);
|
||||
}
|
||||
|
||||
static bool voter_clk_is_local(struct clk *clk)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum handoff voter_clk_handoff(struct clk *clk)
|
||||
{
|
||||
if (!clk->rate)
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
|
||||
/*
|
||||
* Send the default rate to the parent if necessary and update the
|
||||
* software state of the voter clock.
|
||||
*/
|
||||
if (voter_clk_prepare(clk) < 0)
|
||||
return HANDOFF_DISABLED_CLK;
|
||||
|
||||
return HANDOFF_ENABLED_CLK;
|
||||
}
|
||||
|
||||
const struct clk_ops clk_ops_voter = {
|
||||
.prepare = voter_clk_prepare,
|
||||
.unprepare = voter_clk_unprepare,
|
||||
.set_rate = voter_clk_set_rate,
|
||||
.is_enabled = voter_clk_is_enabled,
|
||||
.round_rate = voter_clk_round_rate,
|
||||
.is_local = voter_clk_is_local,
|
||||
.handoff = voter_clk_handoff,
|
||||
};
|
||||
|
||||
static void *sw_vote_clk_dt_parser(struct device *dev,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct clk_voter *v;
|
||||
int rc;
|
||||
u32 temp;
|
||||
|
||||
v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL);
|
||||
if (!v)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
rc = of_property_read_u32(np, "qcom,config-rate", &temp);
|
||||
if (rc) {
|
||||
dt_prop_err(np, "qcom,config-rate", "is missing");
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
v->c.ops = &clk_ops_voter;
|
||||
return msmclk_generic_clk_init(dev, np, &v->c);
|
||||
}
|
||||
MSMCLK_PARSER(sw_vote_clk_dt_parser, "qcom,sw-vote-clk", 0);
|
||||
1407
drivers/clk/msm/clock.c
Normal file
1407
drivers/clk/msm/clock.c
Normal file
File diff suppressed because it is too large
Load diff
54
drivers/clk/msm/clock.h
Normal file
54
drivers/clk/msm/clock.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2014, 2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __DRIVERS_CLK_MSM_CLOCK_H
|
||||
#define __DRIVERS_CLK_MSM_CLOCK_H
|
||||
|
||||
#include <linux/clkdev.h>
|
||||
|
||||
/**
|
||||
* struct clock_init_data - SoC specific clock initialization data
|
||||
* @table: table of lookups to add
|
||||
* @size: size of @table
|
||||
* @pre_init: called before initializing the clock driver.
|
||||
* @post_init: called after registering @table. clock APIs can be called inside.
|
||||
* @late_init: called during late init
|
||||
*/
|
||||
struct clock_init_data {
|
||||
struct list_head list;
|
||||
struct clk_lookup *table;
|
||||
size_t size;
|
||||
void (*pre_init)(void);
|
||||
void (*post_init)(void);
|
||||
int (*late_init)(void);
|
||||
};
|
||||
|
||||
int msm_clock_init(struct clock_init_data *data);
|
||||
int find_vdd_level(struct clk *clk, unsigned long rate);
|
||||
extern struct list_head orphan_clk_list;
|
||||
|
||||
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_COMMON_CLK_MSM)
|
||||
int clock_debug_register(struct clk *clk);
|
||||
void clock_debug_print_enabled(bool print_parent);
|
||||
#elif defined(CONFIG_DEBUG_FS) && defined(CONFIG_COMMON_CLK_QCOM)
|
||||
void clock_debug_print_enabled(bool print_parent);
|
||||
#else
|
||||
static inline int clock_debug_register(struct clk *unused)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void clock_debug_print_enabled(void) { return; }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
721
drivers/clk/msm/gdsc.c
Normal file
721
drivers/clk/msm/gdsc.c
Normal file
|
|
@ -0,0 +1,721 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/regulator/of_regulator.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk/msm-clk.h>
|
||||
|
||||
#define PWR_ON_MASK BIT(31)
|
||||
#define EN_REST_WAIT_MASK (0xF << 20)
|
||||
#define EN_FEW_WAIT_MASK (0xF << 16)
|
||||
#define CLK_DIS_WAIT_MASK (0xF << 12)
|
||||
#define SW_OVERRIDE_MASK BIT(2)
|
||||
#define HW_CONTROL_MASK BIT(1)
|
||||
#define SW_COLLAPSE_MASK BIT(0)
|
||||
#define GMEM_CLAMP_IO_MASK BIT(0)
|
||||
#define GMEM_RESET_MASK BIT(4)
|
||||
#define BCR_BLK_ARES_BIT BIT(0)
|
||||
|
||||
/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */
|
||||
#define EN_REST_WAIT_VAL (0x2 << 20)
|
||||
#define EN_FEW_WAIT_VAL (0x8 << 16)
|
||||
#define CLK_DIS_WAIT_VAL (0x2 << 12)
|
||||
|
||||
#define TIMEOUT_US 100
|
||||
|
||||
struct gdsc {
|
||||
struct regulator_dev *rdev;
|
||||
struct regulator_desc rdesc;
|
||||
void __iomem *gdscr;
|
||||
struct clk **clocks;
|
||||
struct reset_control **reset_clocks;
|
||||
int clock_count;
|
||||
int reset_count;
|
||||
bool toggle_mem;
|
||||
bool toggle_periph;
|
||||
bool toggle_logic;
|
||||
bool resets_asserted;
|
||||
bool root_en;
|
||||
bool force_root_en;
|
||||
int root_clk_idx;
|
||||
bool no_status_check_on_disable;
|
||||
bool is_gdsc_enabled;
|
||||
bool allow_clear;
|
||||
bool reset_aon;
|
||||
void __iomem *domain_addr;
|
||||
void __iomem *hw_ctrl_addr;
|
||||
void __iomem *sw_reset_addr;
|
||||
u32 gds_timeout;
|
||||
};
|
||||
|
||||
enum gdscr_status {
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(gdsc_seq_lock);
|
||||
|
||||
void gdsc_allow_clear_retention(struct regulator *regulator)
|
||||
{
|
||||
struct gdsc *sc = regulator_get_drvdata(regulator);
|
||||
|
||||
if (sc)
|
||||
sc->allow_clear = true;
|
||||
}
|
||||
|
||||
static int poll_gdsc_status(struct gdsc *sc, enum gdscr_status status)
|
||||
{
|
||||
void __iomem *gdscr;
|
||||
int count = sc->gds_timeout;
|
||||
u32 val;
|
||||
|
||||
if (sc->hw_ctrl_addr)
|
||||
gdscr = sc->hw_ctrl_addr;
|
||||
else
|
||||
gdscr = sc->gdscr;
|
||||
|
||||
for (; count > 0; count--) {
|
||||
val = readl_relaxed(gdscr);
|
||||
val &= PWR_ON_MASK;
|
||||
switch (status) {
|
||||
case ENABLED:
|
||||
if (val)
|
||||
return 0;
|
||||
break;
|
||||
case DISABLED:
|
||||
if (!val)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* There is no guarantee about the delay needed for the enable
|
||||
* bit in the GDSCR to be set or reset after the GDSC state
|
||||
* changes. Hence, keep on checking for a reasonable number
|
||||
* of times until the bit is set with the least possible delay
|
||||
* between succeessive tries.
|
||||
*/
|
||||
udelay(1);
|
||||
}
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int gdsc_is_enabled(struct regulator_dev *rdev)
|
||||
{
|
||||
struct gdsc *sc = rdev_get_drvdata(rdev);
|
||||
uint32_t regval;
|
||||
|
||||
if (!sc->toggle_logic)
|
||||
return !sc->resets_asserted;
|
||||
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
if (regval & PWR_ON_MASK) {
|
||||
/*
|
||||
* The GDSC might be turned on due to TZ/HYP vote on the
|
||||
* votable GDS registers. Check the SW_COLLAPSE_MASK to
|
||||
* determine if HLOS has voted for it.
|
||||
*/
|
||||
if (!(regval & SW_COLLAPSE_MASK))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int gdsc_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct gdsc *sc = rdev_get_drvdata(rdev);
|
||||
uint32_t regval, hw_ctrl_regval = 0x0;
|
||||
int i, ret = 0;
|
||||
|
||||
mutex_lock(&gdsc_seq_lock);
|
||||
|
||||
if (sc->root_en || sc->force_root_en)
|
||||
clk_prepare_enable(sc->clocks[sc->root_clk_idx]);
|
||||
|
||||
if (sc->toggle_logic) {
|
||||
if (sc->sw_reset_addr) {
|
||||
regval = readl_relaxed(sc->sw_reset_addr);
|
||||
regval |= BCR_BLK_ARES_BIT;
|
||||
writel_relaxed(regval, sc->sw_reset_addr);
|
||||
/*
|
||||
* BLK_ARES should be kept asserted for 1us before
|
||||
* being de-asserted.
|
||||
*/
|
||||
wmb();
|
||||
udelay(1);
|
||||
|
||||
regval &= ~BCR_BLK_ARES_BIT;
|
||||
writel_relaxed(regval, sc->sw_reset_addr);
|
||||
|
||||
/* Make sure de-assert goes through before continuing */
|
||||
wmb();
|
||||
}
|
||||
|
||||
if (sc->domain_addr) {
|
||||
if (sc->reset_aon) {
|
||||
regval = readl_relaxed(sc->domain_addr);
|
||||
regval |= GMEM_RESET_MASK;
|
||||
writel_relaxed(regval, sc->domain_addr);
|
||||
/*
|
||||
* Keep reset asserted for at-least 1us before
|
||||
* continuing.
|
||||
*/
|
||||
wmb();
|
||||
udelay(1);
|
||||
|
||||
regval &= ~GMEM_RESET_MASK;
|
||||
writel_relaxed(regval, sc->domain_addr);
|
||||
/*
|
||||
* Make sure GMEM_RESET is de-asserted before
|
||||
* continuing.
|
||||
*/
|
||||
wmb();
|
||||
}
|
||||
|
||||
regval = readl_relaxed(sc->domain_addr);
|
||||
regval &= ~GMEM_CLAMP_IO_MASK;
|
||||
writel_relaxed(regval, sc->domain_addr);
|
||||
/*
|
||||
* Make sure CLAMP_IO is de-asserted before continuing.
|
||||
*/
|
||||
wmb();
|
||||
}
|
||||
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
if (regval & HW_CONTROL_MASK) {
|
||||
dev_warn(&rdev->dev, "Invalid enable while %s is under HW control\n",
|
||||
sc->rdesc.name);
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
regval &= ~SW_COLLAPSE_MASK;
|
||||
writel_relaxed(regval, sc->gdscr);
|
||||
|
||||
/* Wait for 8 XO cycles before polling the status bit. */
|
||||
mb();
|
||||
udelay(1);
|
||||
|
||||
ret = poll_gdsc_status(sc, ENABLED);
|
||||
if (ret) {
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
if (sc->hw_ctrl_addr) {
|
||||
hw_ctrl_regval =
|
||||
readl_relaxed(sc->hw_ctrl_addr);
|
||||
dev_warn(&rdev->dev, "%s state (after %d us timeout): 0x%x, GDS_HW_CTRL: 0x%x. Re-polling.\n",
|
||||
sc->rdesc.name, sc->gds_timeout,
|
||||
regval, hw_ctrl_regval);
|
||||
|
||||
ret = poll_gdsc_status(sc, ENABLED);
|
||||
if (ret) {
|
||||
dev_err(&rdev->dev, "%s final state (after additional %d us timeout): 0x%x, GDS_HW_CTRL: 0x%x\n",
|
||||
sc->rdesc.name, sc->gds_timeout,
|
||||
readl_relaxed(sc->gdscr),
|
||||
readl_relaxed(sc->hw_ctrl_addr));
|
||||
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_err(&rdev->dev, "%s enable timed out: 0x%x\n",
|
||||
sc->rdesc.name,
|
||||
regval);
|
||||
udelay(sc->gds_timeout);
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
dev_err(&rdev->dev, "%s final state: 0x%x (%d us after timeout)\n",
|
||||
sc->rdesc.name, regval,
|
||||
sc->gds_timeout);
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < sc->reset_count; i++)
|
||||
reset_control_deassert(sc->reset_clocks[i]);
|
||||
sc->resets_asserted = false;
|
||||
}
|
||||
|
||||
for (i = 0; i < sc->clock_count; i++) {
|
||||
if (unlikely(i == sc->root_clk_idx))
|
||||
continue;
|
||||
if (sc->toggle_mem)
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_MEM);
|
||||
if (sc->toggle_periph)
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_PERIPH);
|
||||
}
|
||||
|
||||
/*
|
||||
* If clocks to this power domain were already on, they will take an
|
||||
* additional 4 clock cycles to re-enable after the rail is enabled.
|
||||
* Delay to account for this. A delay is also needed to ensure clocks
|
||||
* are not enabled within 400ns of enabling power to the memories.
|
||||
*/
|
||||
udelay(1);
|
||||
|
||||
/* Delay to account for staggered memory powerup. */
|
||||
udelay(1);
|
||||
|
||||
if (sc->force_root_en)
|
||||
clk_disable_unprepare(sc->clocks[sc->root_clk_idx]);
|
||||
sc->is_gdsc_enabled = true;
|
||||
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gdsc_disable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct gdsc *sc = rdev_get_drvdata(rdev);
|
||||
uint32_t regval;
|
||||
int i, ret = 0;
|
||||
|
||||
mutex_lock(&gdsc_seq_lock);
|
||||
|
||||
if (sc->force_root_en)
|
||||
clk_prepare_enable(sc->clocks[sc->root_clk_idx]);
|
||||
|
||||
for (i = sc->clock_count-1; i >= 0; i--) {
|
||||
if (unlikely(i == sc->root_clk_idx))
|
||||
continue;
|
||||
if (sc->toggle_mem && sc->allow_clear)
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_MEM);
|
||||
if (sc->toggle_periph && sc->allow_clear)
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_PERIPH);
|
||||
}
|
||||
|
||||
/* Delay to account for staggered memory powerdown. */
|
||||
udelay(1);
|
||||
|
||||
if (sc->toggle_logic) {
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
if (regval & HW_CONTROL_MASK) {
|
||||
dev_warn(&rdev->dev, "Invalid disable while %s is under HW control\n",
|
||||
sc->rdesc.name);
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
regval |= SW_COLLAPSE_MASK;
|
||||
writel_relaxed(regval, sc->gdscr);
|
||||
/* Wait for 8 XO cycles before polling the status bit. */
|
||||
mb();
|
||||
udelay(1);
|
||||
|
||||
if (sc->no_status_check_on_disable) {
|
||||
/*
|
||||
* Add a short delay here to ensure that gdsc_enable
|
||||
* right after it was disabled does not put it in a
|
||||
* weird state.
|
||||
*/
|
||||
udelay(TIMEOUT_US);
|
||||
} else {
|
||||
ret = poll_gdsc_status(sc, DISABLED);
|
||||
if (ret)
|
||||
dev_err(&rdev->dev, "%s disable timed out: 0x%x\n",
|
||||
sc->rdesc.name, regval);
|
||||
}
|
||||
|
||||
if (sc->domain_addr) {
|
||||
regval = readl_relaxed(sc->domain_addr);
|
||||
regval |= GMEM_CLAMP_IO_MASK;
|
||||
writel_relaxed(regval, sc->domain_addr);
|
||||
/* Make sure CLAMP_IO is asserted before continuing. */
|
||||
wmb();
|
||||
}
|
||||
} else {
|
||||
for (i = sc->reset_count-1; i >= 0; i--)
|
||||
reset_control_assert(sc->reset_clocks[i]);
|
||||
sc->resets_asserted = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if gdsc_enable was called for this GDSC. If not, the root
|
||||
* clock will not have been enabled prior to this.
|
||||
*/
|
||||
if ((sc->is_gdsc_enabled && sc->root_en) || sc->force_root_en)
|
||||
clk_disable_unprepare(sc->clocks[sc->root_clk_idx]);
|
||||
sc->is_gdsc_enabled = false;
|
||||
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned int gdsc_get_mode(struct regulator_dev *rdev)
|
||||
{
|
||||
struct gdsc *sc = rdev_get_drvdata(rdev);
|
||||
uint32_t regval;
|
||||
|
||||
mutex_lock(&gdsc_seq_lock);
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
if (regval & HW_CONTROL_MASK)
|
||||
return REGULATOR_MODE_FAST;
|
||||
return REGULATOR_MODE_NORMAL;
|
||||
}
|
||||
|
||||
static int gdsc_set_mode(struct regulator_dev *rdev, unsigned int mode)
|
||||
{
|
||||
struct gdsc *sc = rdev_get_drvdata(rdev);
|
||||
uint32_t regval;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&gdsc_seq_lock);
|
||||
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
|
||||
/*
|
||||
* HW control can only be enable/disabled when SW_COLLAPSE
|
||||
* indicates on.
|
||||
*/
|
||||
if (regval & SW_COLLAPSE_MASK) {
|
||||
dev_err(&rdev->dev, "can't enable hw collapse now\n");
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case REGULATOR_MODE_FAST:
|
||||
/* Turn on HW trigger mode */
|
||||
regval |= HW_CONTROL_MASK;
|
||||
writel_relaxed(regval, sc->gdscr);
|
||||
/*
|
||||
* There may be a race with internal HW trigger signal,
|
||||
* that will result in GDSC going through a power down and
|
||||
* up cycle. In case HW trigger signal is controlled by
|
||||
* firmware that also poll same status bits as we do, FW
|
||||
* might read an 'on' status before the GDSC can finish
|
||||
* power cycle. We wait 1us before returning to ensure
|
||||
* FW can't immediately poll the status bit.
|
||||
*/
|
||||
mb();
|
||||
udelay(1);
|
||||
break;
|
||||
|
||||
case REGULATOR_MODE_NORMAL:
|
||||
/* Turn off HW trigger mode */
|
||||
regval &= ~HW_CONTROL_MASK;
|
||||
writel_relaxed(regval, sc->gdscr);
|
||||
/*
|
||||
* There may be a race with internal HW trigger signal,
|
||||
* that will result in GDSC going through a power down and
|
||||
* up cycle. If we poll too early, status bit will
|
||||
* indicate 'on' before the GDSC can finish the power cycle.
|
||||
* Account for this case by waiting 1us before polling.
|
||||
*/
|
||||
mb();
|
||||
udelay(1);
|
||||
|
||||
ret = poll_gdsc_status(sc, ENABLED);
|
||||
if (ret)
|
||||
dev_err(&rdev->dev, "%s set_mode timed out: 0x%x\n",
|
||||
sc->rdesc.name, regval);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&gdsc_seq_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct regulator_ops gdsc_ops = {
|
||||
.is_enabled = gdsc_is_enabled,
|
||||
.enable = gdsc_enable,
|
||||
.disable = gdsc_disable,
|
||||
.set_mode = gdsc_set_mode,
|
||||
.get_mode = gdsc_get_mode,
|
||||
};
|
||||
|
||||
static int gdsc_probe(struct platform_device *pdev)
|
||||
{
|
||||
static atomic_t gdsc_count = ATOMIC_INIT(-1);
|
||||
struct regulator_config reg_config = {};
|
||||
struct regulator_init_data *init_data;
|
||||
struct resource *res;
|
||||
struct gdsc *sc;
|
||||
uint32_t regval, clk_dis_wait_val = CLK_DIS_WAIT_VAL;
|
||||
bool retain_mem, retain_periph, support_hw_trigger;
|
||||
int i, ret;
|
||||
u32 timeout;
|
||||
|
||||
sc = devm_kzalloc(&pdev->dev, sizeof(struct gdsc), GFP_KERNEL);
|
||||
if (sc == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node,
|
||||
&sc->rdesc);
|
||||
if (init_data == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if (of_get_property(pdev->dev.of_node, "parent-supply", NULL))
|
||||
init_data->supply_regulator = "parent";
|
||||
|
||||
ret = of_property_read_string(pdev->dev.of_node, "regulator-name",
|
||||
&sc->rdesc.name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL)
|
||||
return -EINVAL;
|
||||
sc->gdscr = devm_ioremap(&pdev->dev, res->start, resource_size(res));
|
||||
if (sc->gdscr == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"domain_addr");
|
||||
if (res) {
|
||||
sc->domain_addr = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (sc->domain_addr == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sc->reset_aon = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,reset-aon-logic");
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"sw_reset");
|
||||
if (res) {
|
||||
sc->sw_reset_addr = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (sc->sw_reset_addr == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"hw_ctrl_addr");
|
||||
if (res) {
|
||||
sc->hw_ctrl_addr = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (sc->hw_ctrl_addr == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sc->gds_timeout = TIMEOUT_US;
|
||||
ret = of_property_read_u32(pdev->dev.of_node, "qcom,gds-timeout",
|
||||
&timeout);
|
||||
if (!ret)
|
||||
sc->gds_timeout = timeout;
|
||||
|
||||
sc->clock_count = of_property_count_strings(pdev->dev.of_node,
|
||||
"clock-names");
|
||||
if (sc->clock_count == -EINVAL) {
|
||||
sc->clock_count = 0;
|
||||
} else if (IS_ERR_VALUE((unsigned long)sc->clock_count)) {
|
||||
dev_err(&pdev->dev, "Failed to get clock names\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sc->clocks = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct clk *) * sc->clock_count, GFP_KERNEL);
|
||||
if (!sc->clocks)
|
||||
return -ENOMEM;
|
||||
|
||||
sc->root_clk_idx = -1;
|
||||
|
||||
sc->root_en = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,enable-root-clk");
|
||||
sc->force_root_en = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,force-enable-root-clk");
|
||||
for (i = 0; i < sc->clock_count; i++) {
|
||||
const char *clock_name;
|
||||
|
||||
of_property_read_string_index(pdev->dev.of_node, "clock-names",
|
||||
i, &clock_name);
|
||||
sc->clocks[i] = devm_clk_get(&pdev->dev, clock_name);
|
||||
if (IS_ERR(sc->clocks[i])) {
|
||||
int rc = PTR_ERR(sc->clocks[i]);
|
||||
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "Failed to get %s\n",
|
||||
clock_name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!strcmp(clock_name, "core_root_clk"))
|
||||
sc->root_clk_idx = i;
|
||||
}
|
||||
|
||||
if ((sc->root_en || sc->force_root_en) && (sc->root_clk_idx == -1)) {
|
||||
dev_err(&pdev->dev, "Failed to get root clock name\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sc->rdesc.id = atomic_inc_return(&gdsc_count);
|
||||
sc->rdesc.ops = &gdsc_ops;
|
||||
sc->rdesc.type = REGULATOR_VOLTAGE;
|
||||
sc->rdesc.owner = THIS_MODULE;
|
||||
platform_set_drvdata(pdev, sc);
|
||||
|
||||
/*
|
||||
* Disable HW trigger: collapse/restore occur based on registers writes.
|
||||
* Disable SW override: Use hardware state-machine for sequencing.
|
||||
*/
|
||||
regval = readl_relaxed(sc->gdscr);
|
||||
regval &= ~(HW_CONTROL_MASK | SW_OVERRIDE_MASK);
|
||||
|
||||
if (!of_property_read_u32(pdev->dev.of_node, "qcom,clk-dis-wait-val",
|
||||
&clk_dis_wait_val))
|
||||
clk_dis_wait_val = clk_dis_wait_val << 12;
|
||||
|
||||
/* Configure wait time between states. */
|
||||
regval &= ~(EN_REST_WAIT_MASK | EN_FEW_WAIT_MASK | CLK_DIS_WAIT_MASK);
|
||||
regval |= EN_REST_WAIT_VAL | EN_FEW_WAIT_VAL | clk_dis_wait_val;
|
||||
writel_relaxed(regval, sc->gdscr);
|
||||
|
||||
sc->no_status_check_on_disable =
|
||||
of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,no-status-check-on-disable");
|
||||
retain_mem = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,retain-mem");
|
||||
sc->toggle_mem = !retain_mem;
|
||||
retain_periph = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,retain-periph");
|
||||
sc->toggle_periph = !retain_periph;
|
||||
sc->toggle_logic = !of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,skip-logic-collapse");
|
||||
support_hw_trigger = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,support-hw-trigger");
|
||||
if (support_hw_trigger) {
|
||||
init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_MODE;
|
||||
init_data->constraints.valid_modes_mask |=
|
||||
REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST;
|
||||
}
|
||||
|
||||
if (!sc->toggle_logic) {
|
||||
sc->reset_count = of_property_count_strings(pdev->dev.of_node,
|
||||
"reset-names");
|
||||
if (sc->reset_count == -EINVAL) {
|
||||
sc->reset_count = 0;
|
||||
} else if (IS_ERR_VALUE((unsigned long)sc->reset_count)) {
|
||||
dev_err(&pdev->dev, "Failed to get reset reset names\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sc->reset_clocks = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct reset_control *) *
|
||||
sc->reset_count,
|
||||
GFP_KERNEL);
|
||||
if (!sc->reset_clocks)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < sc->reset_count; i++) {
|
||||
const char *reset_name;
|
||||
|
||||
of_property_read_string_index(pdev->dev.of_node,
|
||||
"reset-names", i, &reset_name);
|
||||
sc->reset_clocks[i] = devm_reset_control_get(&pdev->dev,
|
||||
reset_name);
|
||||
if (IS_ERR(sc->reset_clocks[i])) {
|
||||
int rc = PTR_ERR(sc->reset_clocks[i]);
|
||||
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "Failed to get %s\n",
|
||||
reset_name);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
regval &= ~SW_COLLAPSE_MASK;
|
||||
writel_relaxed(regval, sc->gdscr);
|
||||
|
||||
ret = poll_gdsc_status(sc, ENABLED);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s enable timed out: 0x%x\n",
|
||||
sc->rdesc.name, regval);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
sc->allow_clear = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,disallow-clear");
|
||||
sc->allow_clear = !sc->allow_clear;
|
||||
|
||||
for (i = 0; i < sc->clock_count; i++) {
|
||||
if (retain_mem || (regval & PWR_ON_MASK) || !sc->allow_clear)
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_MEM);
|
||||
else
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_MEM);
|
||||
|
||||
if (retain_periph || (regval & PWR_ON_MASK) || !sc->allow_clear)
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_PERIPH);
|
||||
else
|
||||
clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_PERIPH);
|
||||
}
|
||||
|
||||
reg_config.dev = &pdev->dev;
|
||||
reg_config.init_data = init_data;
|
||||
reg_config.driver_data = sc;
|
||||
reg_config.of_node = pdev->dev.of_node;
|
||||
sc->rdev = regulator_register(&sc->rdesc, ®_config);
|
||||
if (IS_ERR(sc->rdev)) {
|
||||
dev_err(&pdev->dev, "regulator_register(\"%s\") failed.\n",
|
||||
sc->rdesc.name);
|
||||
return PTR_ERR(sc->rdev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gdsc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gdsc *sc = platform_get_drvdata(pdev);
|
||||
|
||||
regulator_unregister(sc->rdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id gdsc_match_table[] = {
|
||||
{ .compatible = "qcom,gdsc" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver gdsc_driver = {
|
||||
.probe = gdsc_probe,
|
||||
.remove = gdsc_remove,
|
||||
.driver = {
|
||||
.name = "gdsc",
|
||||
.of_match_table = gdsc_match_table,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init gdsc_init(void)
|
||||
{
|
||||
return platform_driver_register(&gdsc_driver);
|
||||
}
|
||||
subsys_initcall(gdsc_init);
|
||||
|
||||
static void __exit gdsc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&gdsc_driver);
|
||||
}
|
||||
module_exit(gdsc_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("MSM8974 GDSC power rail regulator driver");
|
||||
748
drivers/clk/msm/msm-clock-controller.c
Normal file
748
drivers/clk/msm/msm-clock-controller.c
Normal file
|
|
@ -0,0 +1,748 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2016-2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "msmclock: %s: " fmt, __func__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/hashtable.h>
|
||||
|
||||
#include <linux/clk/msm-clk-provider.h>
|
||||
#include <soc/qcom/msm-clock-controller.h>
|
||||
#include <soc/qcom/clock-rpm.h>
|
||||
|
||||
/* Protects list operations */
|
||||
static DEFINE_MUTEX(msmclk_lock);
|
||||
static LIST_HEAD(msmclk_parser_list);
|
||||
static u32 msmclk_debug;
|
||||
|
||||
struct hitem {
|
||||
struct hlist_node list;
|
||||
phandle key;
|
||||
void *ptr;
|
||||
};
|
||||
|
||||
int of_property_count_phandles(struct device_node *np, char *propname)
|
||||
{
|
||||
const __be32 *phandle;
|
||||
int size;
|
||||
|
||||
phandle = of_get_property(np, propname, &size);
|
||||
return phandle ? (size / sizeof(*phandle)) : -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_property_count_phandles);
|
||||
|
||||
int of_property_read_phandle_index(struct device_node *np, char *propname,
|
||||
int index, phandle *p)
|
||||
{
|
||||
const __be32 *phandle;
|
||||
int size;
|
||||
|
||||
phandle = of_get_property(np, propname, &size);
|
||||
if ((!phandle) || (size < sizeof(*phandle) * (index + 1)))
|
||||
return -EINVAL;
|
||||
|
||||
*p = be32_to_cpup(phandle + index);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(of_property_read_phandle_index);
|
||||
|
||||
static int generic_vdd_parse_regulators(struct device *dev,
|
||||
struct clk_vdd_class *vdd, struct device_node *np)
|
||||
{
|
||||
int num_regulators, i, rc;
|
||||
char *name = "qcom,regulators";
|
||||
|
||||
num_regulators = of_property_count_phandles(np, name);
|
||||
if (num_regulators <= 0) {
|
||||
dt_prop_err(np, name, "missing dt property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vdd->regulator = devm_kzalloc(dev,
|
||||
sizeof(*vdd->regulator) * num_regulators,
|
||||
GFP_KERNEL);
|
||||
if (!vdd->regulator) {
|
||||
dt_err(np, "memory alloc failure\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_regulators; i++) {
|
||||
phandle p;
|
||||
|
||||
rc = of_property_read_phandle_index(np, name, i, &p);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read phandle\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
vdd->regulator[i] = msmclk_parse_phandle(dev, p);
|
||||
if (IS_ERR(vdd->regulator[i])) {
|
||||
dt_prop_err(np, name, "hashtable lookup failed\n");
|
||||
return PTR_ERR(vdd->regulator[i]);
|
||||
}
|
||||
}
|
||||
|
||||
vdd->num_regulators = num_regulators;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_vdd_parse_levels(struct device *dev,
|
||||
struct clk_vdd_class *vdd, struct device_node *np)
|
||||
{
|
||||
int len, rc;
|
||||
char *name = "qcom,uV-levels";
|
||||
|
||||
if (!of_find_property(np, name, &len)) {
|
||||
dt_prop_err(np, name, "missing dt property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
len /= sizeof(u32);
|
||||
if (len % vdd->num_regulators) {
|
||||
dt_err(np, "mismatch beween qcom,uV-levels and qcom,regulators dt properties\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vdd->num_levels = len / vdd->num_regulators;
|
||||
vdd->vdd_uv = devm_kzalloc(dev, len * sizeof(*vdd->vdd_uv),
|
||||
GFP_KERNEL);
|
||||
vdd->level_votes = devm_kzalloc(dev,
|
||||
vdd->num_levels * sizeof(*vdd->level_votes),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!vdd->vdd_uv || !vdd->level_votes) {
|
||||
dt_err(np, "memory alloc failure\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32_array(np, name, vdd->vdd_uv,
|
||||
vdd->num_levels * vdd->num_regulators);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read u32 array\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Optional Property */
|
||||
name = "qcom,uA-levels";
|
||||
if (!of_find_property(np, name, &len))
|
||||
return 0;
|
||||
|
||||
len /= sizeof(u32);
|
||||
if (len / vdd->num_regulators != vdd->num_levels) {
|
||||
dt_err(np, "size of qcom,uA-levels and qcom,uV-levels must match\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vdd->vdd_ua = devm_kzalloc(dev, len * sizeof(*vdd->vdd_ua),
|
||||
GFP_KERNEL);
|
||||
if (!vdd->vdd_ua)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = of_property_read_u32_array(np, name, vdd->vdd_ua,
|
||||
vdd->num_levels * vdd->num_regulators);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read u32 array\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *simple_vdd_class_dt_parser(struct device *dev,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct clk_vdd_class *vdd;
|
||||
int rc = 0;
|
||||
|
||||
vdd = devm_kzalloc(dev, sizeof(*vdd), GFP_KERNEL);
|
||||
if (!vdd)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mutex_init(&vdd->lock);
|
||||
vdd->class_name = np->name;
|
||||
|
||||
rc = generic_vdd_parse_regulators(dev, vdd, np);
|
||||
rc |= generic_vdd_parse_levels(dev, vdd, np);
|
||||
if (rc) {
|
||||
dt_err(np, "unable to read vdd_class\n");
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
return vdd;
|
||||
}
|
||||
MSMCLK_PARSER(simple_vdd_class_dt_parser, "qcom,simple-vdd-class", 0);
|
||||
|
||||
static int generic_clk_parse_parents(struct device *dev, struct clk *c,
|
||||
struct device_node *np)
|
||||
{
|
||||
int rc;
|
||||
phandle p;
|
||||
char *name = "qcom,parent";
|
||||
|
||||
/* This property is optional */
|
||||
if (!of_find_property(np, name, NULL))
|
||||
return 0;
|
||||
|
||||
rc = of_property_read_phandle_index(np, name, 0, &p);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read phandle\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
c->parent = msmclk_parse_phandle(dev, p);
|
||||
if (IS_ERR(c->parent)) {
|
||||
dt_prop_err(np, name, "hashtable lookup failed\n");
|
||||
return PTR_ERR(c->parent);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_clk_parse_vdd(struct device *dev, struct clk *c,
|
||||
struct device_node *np)
|
||||
{
|
||||
phandle p;
|
||||
int rc;
|
||||
char *name = "qcom,supply-group";
|
||||
|
||||
/* This property is optional */
|
||||
if (!of_find_property(np, name, NULL))
|
||||
return 0;
|
||||
|
||||
rc = of_property_read_phandle_index(np, name, 0, &p);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read phandle\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
c->vdd_class = msmclk_parse_phandle(dev, p);
|
||||
if (IS_ERR(c->vdd_class)) {
|
||||
dt_prop_err(np, name, "hashtable lookup failed\n");
|
||||
return PTR_ERR(c->vdd_class);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_clk_parse_flags(struct device *dev, struct clk *c,
|
||||
struct device_node *np)
|
||||
{
|
||||
int rc;
|
||||
char *name = "qcom,clk-flags";
|
||||
|
||||
/* This property is optional */
|
||||
if (!of_find_property(np, name, NULL))
|
||||
return 0;
|
||||
|
||||
rc = of_property_read_u32(np, name, &c->flags);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read u32\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_clk_parse_fmax(struct device *dev, struct clk *c,
|
||||
struct device_node *np)
|
||||
{
|
||||
u32 prop_len, i;
|
||||
int rc;
|
||||
char *name = "qcom,clk-fmax";
|
||||
|
||||
/* This property is optional */
|
||||
if (!of_find_property(np, name, &prop_len))
|
||||
return 0;
|
||||
|
||||
if (!c->vdd_class) {
|
||||
dt_err(np, "both qcom,clk-fmax and qcom,supply-group must be defined\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
prop_len /= sizeof(u32);
|
||||
if (prop_len % 2) {
|
||||
dt_prop_err(np, name, "bad length\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Value at proplen - 2 is the index of the last entry in fmax array */
|
||||
rc = of_property_read_u32_index(np, name, prop_len - 2, &c->num_fmax);
|
||||
c->num_fmax += 1;
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read u32\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
c->fmax = devm_kzalloc(dev, sizeof(*c->fmax) * c->num_fmax, GFP_KERNEL);
|
||||
if (!c->fmax)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < prop_len; i += 2) {
|
||||
u32 level, value;
|
||||
|
||||
rc = of_property_read_u32_index(np, name, i, &level);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read u32\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32_index(np, name, i + 1, &value);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read u32\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (level >= c->num_fmax) {
|
||||
dt_prop_err(np, name, "must be sorted\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
c->fmax[level] = value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_clk_add_lookup_tbl_entry(struct device *dev, struct clk *c)
|
||||
{
|
||||
struct msmclk_data *drv = dev_get_drvdata(dev);
|
||||
struct clk_lookup *cl;
|
||||
|
||||
if (drv->clk_tbl_size >= drv->max_clk_tbl_size) {
|
||||
dev_err(dev, "child node count should be > clock_count?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cl = drv->clk_tbl + drv->clk_tbl_size;
|
||||
cl->clk = c;
|
||||
drv->clk_tbl_size++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_clk_parse_depends(struct device *dev, struct clk *c,
|
||||
struct device_node *np)
|
||||
{
|
||||
phandle p;
|
||||
int rc;
|
||||
char *name = "qcom,depends";
|
||||
|
||||
/* This property is optional */
|
||||
if (!of_find_property(np, name, NULL))
|
||||
return 0;
|
||||
|
||||
rc = of_property_read_phandle_index(np, name, 0, &p);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read phandle\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
c->depends = msmclk_parse_phandle(dev, p);
|
||||
if (IS_ERR(c->depends)) {
|
||||
dt_prop_err(np, name, "hashtable lookup failed\n");
|
||||
return PTR_ERR(c->depends);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_clk_parse_init_config(struct device *dev, struct clk *c,
|
||||
struct device_node *np)
|
||||
{
|
||||
int rc;
|
||||
u32 temp;
|
||||
char *name = "qcom,always-on";
|
||||
|
||||
c->always_on = of_property_read_bool(np, name);
|
||||
|
||||
name = "qcom,config-rate";
|
||||
/* This property is optional */
|
||||
if (!of_find_property(np, name, NULL))
|
||||
return 0;
|
||||
|
||||
rc = of_property_read_u32(np, name, &temp);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read u32\n");
|
||||
return rc;
|
||||
}
|
||||
c->init_rate = temp;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void *msmclk_generic_clk_init(struct device *dev, struct device_node *np,
|
||||
struct clk *c)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* CLK_INIT macro */
|
||||
spin_lock_init(&c->lock);
|
||||
mutex_init(&c->prepare_lock);
|
||||
INIT_LIST_HEAD(&c->children);
|
||||
INIT_LIST_HEAD(&c->siblings);
|
||||
INIT_LIST_HEAD(&c->list);
|
||||
c->dbg_name = np->name;
|
||||
|
||||
rc = generic_clk_add_lookup_tbl_entry(dev, c);
|
||||
rc |= generic_clk_parse_flags(dev, c, np);
|
||||
rc |= generic_clk_parse_parents(dev, c, np);
|
||||
rc |= generic_clk_parse_vdd(dev, c, np);
|
||||
rc |= generic_clk_parse_fmax(dev, c, np);
|
||||
rc |= generic_clk_parse_depends(dev, c, np);
|
||||
rc |= generic_clk_parse_init_config(dev, c, np);
|
||||
|
||||
if (rc) {
|
||||
dt_err(np, "unable to read clk\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static struct msmclk_parser *msmclk_parser_lookup(struct device_node *np)
|
||||
{
|
||||
struct msmclk_parser *item;
|
||||
|
||||
list_for_each_entry(item, &msmclk_parser_list, list) {
|
||||
if (of_device_is_compatible(np, item->compatible))
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void msmclk_parser_register(struct msmclk_parser *item)
|
||||
{
|
||||
mutex_lock(&msmclk_lock);
|
||||
list_add(&item->list, &msmclk_parser_list);
|
||||
mutex_unlock(&msmclk_lock);
|
||||
}
|
||||
|
||||
static int msmclk_htable_add(struct device *dev, void *result, phandle key);
|
||||
|
||||
void *msmclk_parse_dt_node(struct device *dev, struct device_node *np)
|
||||
{
|
||||
struct msmclk_parser *parser;
|
||||
phandle key;
|
||||
void *result;
|
||||
int rc;
|
||||
|
||||
key = np->phandle;
|
||||
result = msmclk_lookup_phandle(dev, key);
|
||||
if (!result)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (!of_device_is_available(np)) {
|
||||
dt_err(np, "node is disabled\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
parser = msmclk_parser_lookup(np);
|
||||
if (IS_ERR_OR_NULL(parser)) {
|
||||
dt_err(np, "no parser found\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/* This may return -EPROBE_DEFER */
|
||||
result = parser->parsedt(dev, np);
|
||||
if (IS_ERR(result)) {
|
||||
dt_err(np, "parsedt failed");
|
||||
return result;
|
||||
}
|
||||
|
||||
rc = msmclk_htable_add(dev, result, key);
|
||||
if (rc)
|
||||
return ERR_PTR(rc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void *msmclk_parse_phandle(struct device *dev, phandle key)
|
||||
{
|
||||
struct hitem *item;
|
||||
struct device_node *np;
|
||||
struct msmclk_data *drv = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* the default phandle value is 0. Since hashtable keys must
|
||||
* be unique, reject the default value.
|
||||
*/
|
||||
if (!key)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
hash_for_each_possible(drv->htable, item, list, key) {
|
||||
if (item->key == key)
|
||||
return item->ptr;
|
||||
}
|
||||
|
||||
np = of_find_node_by_phandle(key);
|
||||
if (!np)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return msmclk_parse_dt_node(dev, np);
|
||||
}
|
||||
EXPORT_SYMBOL(msmclk_parse_phandle);
|
||||
|
||||
void *msmclk_lookup_phandle(struct device *dev, phandle key)
|
||||
{
|
||||
struct hitem *item;
|
||||
struct msmclk_data *drv = dev_get_drvdata(dev);
|
||||
|
||||
hash_for_each_possible(drv->htable, item, list, key) {
|
||||
if (item->key == key)
|
||||
return item->ptr;
|
||||
}
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
EXPORT_SYMBOL(msmclk_lookup_phandle);
|
||||
|
||||
static int msmclk_htable_add(struct device *dev, void *data, phandle key)
|
||||
{
|
||||
struct hitem *item;
|
||||
struct msmclk_data *drv = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* If there are no phandle references to a node, key == 0. However, if
|
||||
* there is a second node like this, both will have key == 0. This
|
||||
* violates the requirement that hashtable keys be unique. Skip it.
|
||||
*/
|
||||
if (!key)
|
||||
return 0;
|
||||
|
||||
if (!IS_ERR(msmclk_lookup_phandle(dev, key))) {
|
||||
struct device_node *np = of_find_node_by_phandle(key);
|
||||
|
||||
dev_err(dev, "attempt to add duplicate entry for %s\n",
|
||||
np ? np->name : "NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
item = devm_kzalloc(dev, sizeof(*item), GFP_KERNEL);
|
||||
if (!item)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_HLIST_NODE(&item->list);
|
||||
item->key = key;
|
||||
item->ptr = data;
|
||||
|
||||
hash_add(drv->htable, &item->list, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Currently, regulators are the only elements capable of probe deferral.
|
||||
* Check them first to handle probe deferal efficiently.
|
||||
*/
|
||||
static int get_ext_regulators(struct device *dev)
|
||||
{
|
||||
int num_strings, i, rc;
|
||||
struct device_node *np;
|
||||
void *item;
|
||||
char *name = "qcom,regulator-names";
|
||||
|
||||
np = dev->of_node;
|
||||
/* This property is optional */
|
||||
num_strings = of_property_count_strings(np, name);
|
||||
if (num_strings <= 0)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < num_strings; i++) {
|
||||
const char *str;
|
||||
char buf[50];
|
||||
phandle key;
|
||||
|
||||
rc = of_property_read_string_index(np, name, i, &str);
|
||||
if (rc) {
|
||||
dt_prop_err(np, name, "unable to read string\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
item = devm_regulator_get(dev, str);
|
||||
if (IS_ERR(item)) {
|
||||
dev_err(dev, "Failed to get regulator: %s\n", str);
|
||||
return PTR_ERR(item);
|
||||
}
|
||||
|
||||
snprintf(buf, ARRAY_SIZE(buf), "%s-supply", str);
|
||||
rc = of_property_read_phandle_index(np, buf, 0, &key);
|
||||
if (rc) {
|
||||
dt_prop_err(np, buf, "unable to read phandle\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = msmclk_htable_add(dev, item, key);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct clk *msmclk_clk_get(struct of_phandle_args *clkspec, void *data)
|
||||
{
|
||||
phandle key;
|
||||
struct clk *c = ERR_PTR(-ENOENT);
|
||||
|
||||
key = clkspec->args[0];
|
||||
c = msmclk_lookup_phandle(data, key);
|
||||
|
||||
if (!IS_ERR(c) && !(c->flags & CLKFLAG_INIT_DONE))
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static void *regulator_dt_parser(struct device *dev, struct device_node *np)
|
||||
{
|
||||
dt_err(np, "regulators should be handled in probe()");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
MSMCLK_PARSER(regulator_dt_parser, "qcom,rpm-smd-regulator", 0);
|
||||
|
||||
static void *msmclk_dt_parser(struct device *dev, struct device_node *np)
|
||||
{
|
||||
dt_err(np, "calling into other clock controllers isn't allowed");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
MSMCLK_PARSER(msmclk_dt_parser, "qcom,msm-clock-controller", 0);
|
||||
|
||||
static struct msmclk_data *msmclk_drv_init(struct device *dev)
|
||||
{
|
||||
struct msmclk_data *drv;
|
||||
size_t size;
|
||||
|
||||
drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
|
||||
if (!drv)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
dev_set_drvdata(dev, drv);
|
||||
|
||||
drv->dev = dev;
|
||||
INIT_LIST_HEAD(&drv->list);
|
||||
|
||||
/* This overestimates size */
|
||||
drv->max_clk_tbl_size = of_get_child_count(dev->of_node);
|
||||
size = sizeof(*drv->clk_tbl) * drv->max_clk_tbl_size;
|
||||
drv->clk_tbl = devm_kzalloc(dev, size, GFP_KERNEL);
|
||||
if (!drv->clk_tbl)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
hash_init(drv->htable);
|
||||
return drv;
|
||||
}
|
||||
|
||||
static int msmclk_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
struct device *dev;
|
||||
struct msmclk_data *drv;
|
||||
struct device_node *child;
|
||||
void *result;
|
||||
int rc = 0;
|
||||
|
||||
dev = &pdev->dev;
|
||||
drv = msmclk_drv_init(dev);
|
||||
if (IS_ERR(drv))
|
||||
return PTR_ERR(drv);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cc-base");
|
||||
if (!res) {
|
||||
dt_err(dev->of_node, "missing cc-base\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
drv->base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!drv->base) {
|
||||
dev_err(dev, "ioremap failed for drv->base\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = msmclk_htable_add(dev, drv, dev->of_node->phandle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = enable_rpm_scaling();
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = get_ext_regulators(dev);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* Returning -EPROBE_DEFER here is inefficient due to
|
||||
* destroying work 'unnecessarily'
|
||||
*/
|
||||
for_each_available_child_of_node(dev->of_node, child) {
|
||||
result = msmclk_parse_dt_node(dev, child);
|
||||
if (!IS_ERR(result))
|
||||
continue;
|
||||
if (!msmclk_debug)
|
||||
return PTR_ERR(result);
|
||||
/*
|
||||
* Parse and report all errors instead of immediately
|
||||
* exiting. Return the first error code.
|
||||
*/
|
||||
if (!rc)
|
||||
rc = PTR_ERR(result);
|
||||
}
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = of_clk_add_provider(dev->of_node, msmclk_clk_get, dev);
|
||||
if (rc) {
|
||||
dev_err(dev, "of_clk_add_provider failed\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* can't fail after registering clocks, because users may have
|
||||
* gotten clock references. Failing would delete the memory.
|
||||
*/
|
||||
WARN_ON(msm_clock_register(drv->clk_tbl, drv->clk_tbl_size));
|
||||
dev_info(dev, "registered clocks\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id msmclk_match_table[] = {
|
||||
{.compatible = "qcom,msm-clock-controller"},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver msmclk_driver = {
|
||||
.probe = msmclk_probe,
|
||||
.driver = {
|
||||
.name = "msm-clock-controller",
|
||||
.of_match_table = msmclk_match_table,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static bool initialized;
|
||||
int __init msmclk_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (initialized)
|
||||
return 0;
|
||||
|
||||
rc = platform_driver_register(&msmclk_driver);
|
||||
if (rc)
|
||||
return rc;
|
||||
initialized = true;
|
||||
return rc;
|
||||
}
|
||||
arch_initcall(msmclk_init);
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue