From: David Brown <davidb@codeaurora.org>
To: David Brown <davidb@codeaurora.org>,
Daniel Walker <dwalker@fifo99.com>,
Bryan Huntsman <bryanh@codeaurora.org>,
Russell King <linux@arm.linux.org.uk>
Cc: Stephen Boyd <sboyd@codeaurora.org>,
linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
linux-arm-kernel@lists.infradead.org
Subject: [RFC PATCH 15/34] msm: clock: Add local clock control framework
Date: Wed, 2 Nov 2011 11:36:12 -0700 [thread overview]
Message-ID: <1320258991-22325-16-git-send-email-davidb@codeaurora.org> (raw)
In-Reply-To: <1320258991-22325-1-git-send-email-davidb@codeaurora.org>
From: Stephen Boyd <sboyd@codeaurora.org>
Based on code originally written by Saravana Kannan, rewritten by
Matt Wagantall.
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: David Brown <davidb@codeaurora.org>
---
arch/arm/mach-msm/clock-local.c | 700 +++++++++++++++++++++++++++++++++++++++
arch/arm/mach-msm/clock-local.h | 242 ++++++++++++++
2 files changed, 942 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-msm/clock-local.c
create mode 100644 arch/arm/mach-msm/clock-local.h
diff --git a/arch/arm/mach-msm/clock-local.c b/arch/arm/mach-msm/clock-local.c
new file mode 100644
index 0000000..7a6dc5d
--- /dev/null
+++ b/arch/arm/mach-msm/clock-local.c
@@ -0,0 +1,700 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. 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/kernel.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/ctype.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <mach/clk.h>
+
+#include "clock.h"
+#include "clock-local.h"
+
+/*
+ * When enabling/disabling a clock, check the halt bit up to this number
+ * number of times (with a 1 us delay in between) before continuing.
+ */
+#define HALT_CHECK_MAX_LOOPS 100
+/* For clock without halt checking, wait this long after enables/disables. */
+#define HALT_CHECK_DELAY_US 10
+
+DEFINE_SPINLOCK(local_clock_reg_lock);
+struct clk_freq_tbl local_dummy_freq = F_END;
+
+unsigned local_sys_vdd_votes[NUM_SYS_VDD_LEVELS];
+static DEFINE_SPINLOCK(sys_vdd_vote_lock);
+
+/*
+ * Common Set-Rate Functions
+ */
+
+/* For clocks with MND dividers. */
+void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf)
+{
+ u32 ns_reg_val, ctl_reg_val;
+
+ /* Assert MND reset. */
+ ns_reg_val = readl_relaxed(clk->ns_reg);
+ ns_reg_val |= BIT(7);
+ writel_relaxed(ns_reg_val, clk->ns_reg);
+
+ /* Program M and D values. */
+ writel_relaxed(nf->md_val, clk->md_reg);
+
+ /* If the clock has a separate CC register, program it. */
+ if (clk->ns_reg != clk->b.ctl_reg) {
+ ctl_reg_val = readl_relaxed(clk->b.ctl_reg);
+ ctl_reg_val &= ~(clk->ctl_mask);
+ ctl_reg_val |= nf->ctl_val;
+ writel_relaxed(ctl_reg_val, clk->b.ctl_reg);
+ }
+
+ /* Deassert MND reset. */
+ ns_reg_val &= ~BIT(7);
+ writel_relaxed(ns_reg_val, clk->ns_reg);
+}
+
+void set_rate_nop(struct rcg_clk *clk, struct clk_freq_tbl *nf)
+{
+ /*
+ * Nothing to do for fixed-rate or integer-divider clocks. Any settings
+ * in NS registers are applied in the enable path, since power can be
+ * saved by leaving an un-clocked or slowly-clocked source selected
+ * until the clock is enabled.
+ */
+}
+
+int (*soc_update_sys_vdd)(enum sys_vdd_level level);
+/*
+ * SYS_VDD voting functions
+ */
+
+/* Update system voltage level given the current votes. */
+static int local_update_sys_vdd(void)
+{
+ static int cur_level = NUM_SYS_VDD_LEVELS;
+ int level, rc = 0;
+
+ if (local_sys_vdd_votes[HIGH])
+ level = HIGH;
+ else if (local_sys_vdd_votes[NOMINAL])
+ level = NOMINAL;
+ else if (local_sys_vdd_votes[LOW])
+ level = LOW;
+ else
+ level = NONE;
+
+ if (level == cur_level)
+ return rc;
+
+ rc = soc_update_sys_vdd(level);
+ if (!rc)
+ cur_level = level;
+
+ return rc;
+}
+
+/* Vote for a system voltage level. */
+int local_vote_sys_vdd(unsigned level)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ /* Bounds checking. */
+ if (level >= ARRAY_SIZE(local_sys_vdd_votes))
+ return -EINVAL;
+
+ spin_lock_irqsave(&sys_vdd_vote_lock, flags);
+ local_sys_vdd_votes[level]++;
+ rc = local_update_sys_vdd();
+ if (rc)
+ local_sys_vdd_votes[level]--;
+ spin_unlock_irqrestore(&sys_vdd_vote_lock, flags);
+
+ return rc;
+}
+
+/* Remove vote for a system voltage level. */
+int local_unvote_sys_vdd(unsigned level)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ /* Bounds checking. */
+ if (level >= ARRAY_SIZE(local_sys_vdd_votes))
+ return -EINVAL;
+
+ spin_lock_irqsave(&sys_vdd_vote_lock, flags);
+
+ if (WARN(!local_sys_vdd_votes[level],
+ "Reference counts are incorrect for level %d!\n", level))
+ goto out;
+
+ local_sys_vdd_votes[level]--;
+ rc = local_update_sys_vdd();
+ if (rc)
+ local_sys_vdd_votes[level]++;
+out:
+ spin_unlock_irqrestore(&sys_vdd_vote_lock, flags);
+ return rc;
+}
+/*
+ * Clock enable/disable functions
+ */
+
+/* Return non-zero if a clock status registers shows the clock is halted. */
+static int branch_clk_is_halted(const struct branch *clk)
+{
+ int invert = (clk->halt_check == ENABLE);
+ int status_bit = readl_relaxed(clk->halt_reg) & BIT(clk->halt_bit);
+ return invert ? !status_bit : status_bit;
+}
+
+static void __branch_clk_enable_reg(const struct branch *clk, const char *name)
+{
+ u32 reg_val;
+
+ if (clk->en_mask) {
+ reg_val = readl_relaxed(clk->ctl_reg);
+ reg_val |= clk->en_mask;
+ writel_relaxed(reg_val, clk->ctl_reg);
+ }
+
+ /*
+ * Use a memory barrier since some halt status registers are
+ * not within the same 1K segment as the branch/root enable
+ * registers. It's also needed in the udelay() case to ensure
+ * the delay starts after the branch enable.
+ */
+ mb();
+
+ /* Wait for clock to enable before returning. */
+ if (clk->halt_check == DELAY)
+ udelay(HALT_CHECK_DELAY_US);
+ else if (clk->halt_check == ENABLE || clk->halt_check == HALT
+ || clk->halt_check == ENABLE_VOTED
+ || clk->halt_check == HALT_VOTED) {
+ int count;
+
+ /* Wait up to HALT_CHECK_MAX_LOOPS for clock to enable. */
+ for (count = HALT_CHECK_MAX_LOOPS; branch_clk_is_halted(clk)
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "%s status stuck at 'off'", name);
+ }
+}
+
+/* Perform any register operations required to enable the clock. */
+static void __rcg_clk_enable_reg(struct rcg_clk *clk)
+{
+ u32 reg_val;
+ void __iomem *const reg = clk->b.ctl_reg;
+
+ WARN(clk->current_freq == &local_dummy_freq,
+ "Attempting to enable %s before setting its rate. "
+ "Set the rate first!\n", clk->c.dbg_name);
+
+ /*
+ * Program the NS register, if applicable. NS registers are not
+ * set in the set_rate path because power can be saved by deferring
+ * the selection of a clocked source until the clock is enabled.
+ */
+ if (clk->ns_mask) {
+ reg_val = readl_relaxed(clk->ns_reg);
+ reg_val &= ~(clk->ns_mask);
+ reg_val |= (clk->current_freq->ns_val & clk->ns_mask);
+ writel_relaxed(reg_val, clk->ns_reg);
+ }
+
+ /* Enable MN counter, if applicable. */
+ reg_val = readl_relaxed(reg);
+ if (clk->current_freq->mnd_en_mask) {
+ reg_val |= clk->current_freq->mnd_en_mask;
+ writel_relaxed(reg_val, reg);
+ }
+ /* Enable root. */
+ if (clk->root_en_mask) {
+ reg_val |= clk->root_en_mask;
+ writel_relaxed(reg_val, reg);
+ }
+ __branch_clk_enable_reg(&clk->b, clk->c.dbg_name);
+}
+
+/* Perform any register operations required to disable the branch. */
+static u32 __branch_clk_disable_reg(const struct branch *clk, const char *name)
+{
+ u32 reg_val;
+
+ reg_val = readl_relaxed(clk->ctl_reg);
+ if (clk->en_mask) {
+ reg_val &= ~(clk->en_mask);
+ writel_relaxed(reg_val, clk->ctl_reg);
+ }
+
+ /*
+ * Use a memory barrier since some halt status registers are
+ * not within the same K segment as the branch/root enable
+ * registers. It's also needed in the udelay() case to ensure
+ * the delay starts after the branch disable.
+ */
+ mb();
+
+ /* Wait for clock to disable before continuing. */
+ if (clk->halt_check == DELAY || clk->halt_check == ENABLE_VOTED
+ || clk->halt_check == HALT_VOTED)
+ udelay(HALT_CHECK_DELAY_US);
+ else if (clk->halt_check == ENABLE || clk->halt_check == HALT) {
+ int count;
+
+ /* Wait up to HALT_CHECK_MAX_LOOPS for clock to disable. */
+ for (count = HALT_CHECK_MAX_LOOPS; !branch_clk_is_halted(clk)
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "%s status stuck at 'on'", name);
+ }
+
+ return reg_val;
+}
+
+/* Perform any register operations required to disable the generator. */
+static void __rcg_clk_disable_reg(struct rcg_clk *clk)
+{
+ void __iomem *const reg = clk->b.ctl_reg;
+ u32 reg_val;
+
+ reg_val = __branch_clk_disable_reg(&clk->b, clk->c.dbg_name);
+ /* Disable root. */
+ if (clk->root_en_mask) {
+ reg_val &= ~(clk->root_en_mask);
+ writel_relaxed(reg_val, reg);
+ }
+ /* Disable MN counter, if applicable. */
+ if (clk->current_freq->mnd_en_mask) {
+ reg_val &= ~(clk->current_freq->mnd_en_mask);
+ writel_relaxed(reg_val, reg);
+ }
+ /*
+ * Program NS register to low-power value with an un-clocked or
+ * slowly-clocked source selected.
+ */
+ if (clk->ns_mask) {
+ reg_val = readl_relaxed(clk->ns_reg);
+ reg_val &= ~(clk->ns_mask);
+ reg_val |= (clk->freq_tbl->ns_val & clk->ns_mask);
+ writel_relaxed(reg_val, clk->ns_reg);
+ }
+}
+
+static void _rcg_clk_enable(struct rcg_clk *clk)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __rcg_clk_enable_reg(clk);
+ clk->enabled = true;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+static void _rcg_clk_disable(struct rcg_clk *clk)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __rcg_clk_disable_reg(clk);
+ clk->enabled = false;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+/* Enable a clock and any related power rail. */
+int rcg_clk_enable(struct clk *c)
+{
+ int rc;
+ struct rcg_clk *clk = to_rcg_clk(c);
+
+ rc = local_vote_sys_vdd(clk->current_freq->sys_vdd);
+ if (rc)
+ goto err_vdd;
+ rc = clk_enable(clk->depends);
+ if (rc)
+ goto err_dep;
+ _rcg_clk_enable(clk);
+ return rc;
+
+err_dep:
+ local_unvote_sys_vdd(clk->current_freq->sys_vdd);
+err_vdd:
+ return rc;
+}
+
+/* Disable a clock and any related power rail. */
+void rcg_clk_disable(struct clk *c)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+
+ _rcg_clk_disable(clk);
+ clk_disable(clk->depends);
+ local_unvote_sys_vdd(clk->current_freq->sys_vdd);
+}
+
+/* Turn off a clock at boot, without checking refcounts or disabling depends. */
+void rcg_clk_auto_off(struct clk *c)
+{
+ _rcg_clk_disable(to_rcg_clk(c));
+}
+
+/*
+ * Frequency-related functions
+ */
+
+/* Set a clock's frequency. */
+static int _rcg_clk_set_rate(struct rcg_clk *clk, struct clk_freq_tbl *nf)
+{
+ struct clk_freq_tbl *cf;
+ int rc = 0;
+ struct clk *chld;
+ unsigned long flags;
+
+ spin_lock_irqsave(&clk->c.lock, flags);
+
+ /* Check if frequency is actually changed. */
+ cf = clk->current_freq;
+ if (nf == cf)
+ goto unlock;
+
+ if (clk->enabled) {
+ /* Vote for voltage and source for new freq. */
+ rc = local_vote_sys_vdd(nf->sys_vdd);
+ if (rc)
+ goto unlock;
+ rc = clk_enable(nf->src_clk);
+ if (rc) {
+ local_unvote_sys_vdd(nf->sys_vdd);
+ goto unlock;
+ }
+ }
+
+ spin_lock(&local_clock_reg_lock);
+
+ /* Disable all branches to prevent glitches. */
+ list_for_each_entry(chld, &clk->c.children, siblings) {
+ struct branch_clk *x = to_branch_clk(chld);
+ /* Don't bother turning off if it is already off.
+ * Checking ch->enabled is cheaper (cache) than reading
+ * and writing to a register (uncached/unbuffered). */
+ if (x->enabled)
+ __branch_clk_disable_reg(&x->b, x->c.dbg_name);
+ }
+ if (clk->enabled)
+ __rcg_clk_disable_reg(clk);
+
+ /* Perform clock-specific frequency switch operations. */
+ BUG_ON(!clk->set_rate);
+ clk->set_rate(clk, nf);
+
+ /*
+ * Current freq must be updated before __rcg_clk_enable_reg()
+ * is called to make sure the MNCNTR_EN bit is set correctly.
+ */
+ clk->current_freq = nf;
+
+ if (clk->enabled)
+ __rcg_clk_enable_reg(clk);
+ /* Enable only branches that were ON before. */
+ list_for_each_entry(chld, &clk->c.children, siblings) {
+ struct branch_clk *x = to_branch_clk(chld);
+ if (x->enabled)
+ __branch_clk_enable_reg(&x->b, x->c.dbg_name);
+ }
+
+ spin_unlock(&local_clock_reg_lock);
+
+ /* Release requirements of the old freq. */
+ if (clk->enabled) {
+ clk_disable(cf->src_clk);
+ local_unvote_sys_vdd(cf->sys_vdd);
+ }
+unlock:
+ spin_unlock_irqrestore(&clk->c.lock, flags);
+
+ return rc;
+}
+
+/* Set a clock to an exact rate. */
+int rcg_clk_set_rate(struct clk *c, unsigned rate)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ struct clk_freq_tbl *nf;
+
+ for (nf = clk->freq_tbl; nf->freq_hz != FREQ_END
+ && nf->freq_hz != rate; nf++)
+ ;
+
+ if (nf->freq_hz == FREQ_END)
+ return -EINVAL;
+
+ return _rcg_clk_set_rate(clk, nf);
+}
+
+/* Set a clock to a rate greater than some minimum. */
+int rcg_clk_set_min_rate(struct clk *c, unsigned rate)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ struct clk_freq_tbl *nf;
+
+ for (nf = clk->freq_tbl; nf->freq_hz != FREQ_END
+ && nf->freq_hz < rate; nf++)
+ ;
+
+ if (nf->freq_hz == FREQ_END)
+ return -EINVAL;
+
+ return _rcg_clk_set_rate(clk, nf);
+}
+
+/* Set a clock to a maximum rate. */
+int rcg_clk_set_max_rate(struct clk *clk, unsigned rate)
+{
+ return -EPERM;
+}
+
+/* Get the currently-set rate of a clock in Hz. */
+unsigned rcg_clk_get_rate(struct clk *c)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ unsigned long flags;
+ unsigned ret = 0;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ ret = clk->current_freq->freq_hz;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ /*
+ * Return 0 if the rate has never been set. Might not be correct,
+ * but it's good enough.
+ */
+ if (ret == FREQ_END)
+ ret = 0;
+
+ return ret;
+}
+
+/* Check if a clock is currently enabled. */
+unsigned rcg_clk_is_enabled(struct clk *clk)
+{
+ return to_rcg_clk(clk)->enabled;
+}
+
+/* Return a supported rate that's at least the specified rate. */
+long rcg_clk_round_rate(struct clk *c, unsigned rate)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ struct clk_freq_tbl *f;
+
+ for (f = clk->freq_tbl; f->freq_hz != FREQ_END; f++)
+ if (f->freq_hz >= rate)
+ return f->freq_hz;
+
+ return -EPERM;
+}
+
+bool local_clk_is_local(struct clk *clk)
+{
+ return true;
+}
+
+struct clk *rcg_clk_get_parent(struct clk *clk)
+{
+ return to_rcg_clk(clk)->current_freq->src_clk;
+}
+
+static int pll_vote_clk_enable(struct clk *clk)
+{
+ u32 ena;
+ unsigned long flags;
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ ena = readl_relaxed(pll->en_reg);
+ ena |= pll->en_mask;
+ writel_relaxed(ena, pll->en_reg);
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ /* Wait until PLL is enabled */
+ while ((readl_relaxed(pll->status_reg) & BIT(16)) == 0)
+ cpu_relax();
+
+ return 0;
+}
+
+static void pll_vote_clk_disable(struct clk *clk)
+{
+ u32 ena;
+ unsigned long flags;
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ ena = readl_relaxed(pll->en_reg);
+ ena &= ~(pll->en_mask);
+ writel_relaxed(ena, pll->en_reg);
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+static unsigned pll_vote_clk_get_rate(struct clk *clk)
+{
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+ return pll->rate;
+}
+
+static struct clk *pll_vote_clk_get_parent(struct clk *clk)
+{
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+ return pll->parent;
+}
+
+static unsigned pll_vote_clk_is_enabled(struct clk *clk)
+{
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+ return !!(readl_relaxed(pll->status_reg) & BIT(16));
+}
+
+struct clk_ops clk_ops_pll_vote = {
+ .enable = pll_vote_clk_enable,
+ .disable = pll_vote_clk_disable,
+ .is_enabled = pll_vote_clk_is_enabled,
+ .get_rate = pll_vote_clk_get_rate,
+ .get_parent = pll_vote_clk_get_parent,
+ .is_local = local_clk_is_local,
+};
+
+struct clk_ops clk_ops_gnd = {
+ .get_rate = fixed_clk_get_rate,
+ .is_local = local_clk_is_local,
+};
+
+struct fixed_clk gnd_clk = {
+ .c = {
+ .dbg_name = "ground_clk",
+ .ops = &clk_ops_gnd,
+ CLK_INIT(gnd_clk.c),
+ },
+};
+
+int branch_clk_enable(struct clk *clk)
+{
+ int rc;
+ unsigned long flags;
+ struct branch_clk *branch = to_branch_clk(clk);
+
+ rc = clk_enable(branch->depends);
+ if (rc)
+ return rc;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __branch_clk_enable_reg(&branch->b, branch->c.dbg_name);
+ branch->enabled = true;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ return 0;
+}
+
+void branch_clk_disable(struct clk *clk)
+{
+ unsigned long flags;
+ struct branch_clk *branch = to_branch_clk(clk);
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __branch_clk_disable_reg(&branch->b, branch->c.dbg_name);
+ branch->enabled = false;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ clk_disable(branch->depends);
+}
+
+struct clk *branch_clk_get_parent(struct clk *clk)
+{
+ struct branch_clk *branch = to_branch_clk(clk);
+ return branch->parent;
+}
+
+int branch_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ /*
+ * We setup the parent pointer at init time in msm_clock_init().
+ * This check is to make sure drivers can't change the parent.
+ */
+ if (parent && list_empty(&clk->siblings)) {
+ list_add(&clk->siblings, &parent->children);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+unsigned branch_clk_is_enabled(struct clk *clk)
+{
+ struct branch_clk *branch = to_branch_clk(clk);
+ return branch->enabled;
+}
+
+void branch_clk_auto_off(struct clk *clk)
+{
+ struct branch_clk *branch = to_branch_clk(clk);
+ unsigned long flags;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __branch_clk_disable_reg(&branch->b, branch->c.dbg_name);
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+int branch_reset(struct branch *clk, enum clk_reset_action action)
+{
+ int ret = 0;
+ u32 reg_val;
+ unsigned long flags;
+
+ if (!clk->reset_reg)
+ return -EPERM;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+
+ reg_val = readl_relaxed(clk->reset_reg);
+ switch (action) {
+ case CLK_RESET_ASSERT:
+ reg_val |= clk->reset_mask;
+ break;
+ case CLK_RESET_DEASSERT:
+ reg_val &= ~(clk->reset_mask);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ writel_relaxed(reg_val, clk->reset_reg);
+
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ /* Make sure write is issued before returning. */
+ mb();
+
+ return ret;
+}
+
+int branch_clk_reset(struct clk *clk, enum clk_reset_action action)
+{
+ return branch_reset(&to_branch_clk(clk)->b, action);
+}
diff --git a/arch/arm/mach-msm/clock-local.h b/arch/arm/mach-msm/clock-local.h
new file mode 100644
index 0000000..a7c9001
--- /dev/null
+++ b/arch/arm/mach-msm/clock-local.h
@@ -0,0 +1,242 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. 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.
+ */
+
+#ifndef __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H
+#define __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H
+
+#include <linux/spinlock.h>
+#include "clock.h"
+
+/*
+ * Bit manipulation macros
+ */
+#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb)
+#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb))
+
+/*
+ * Halt/Status Checking Mode Macros
+ */
+#define HALT 0 /* Bit pol: 1 = halted */
+#define NOCHECK 1 /* No bit to check, do nothing */
+#define HALT_VOTED 2 /* Bit pol: 1 = halted; delay on disable */
+#define ENABLE 3 /* Bit pol: 1 = running */
+#define ENABLE_VOTED 4 /* Bit pol: 1 = running; delay on disable */
+#define DELAY 5 /* No bit to check, just delay */
+
+/*
+ * Generic frequency-definition structs and macros
+ */
+struct clk_freq_tbl {
+ const uint32_t freq_hz;
+ struct clk *src_clk;
+ const uint32_t md_val;
+ const uint32_t ns_val;
+ const uint32_t ctl_val;
+ uint32_t mnd_en_mask;
+ const unsigned sys_vdd;
+ void *const extra_freq_data;
+};
+
+#define F_RAW(f, sc, m_v, n_v, c_v, m_m, v, e) { \
+ .freq_hz = f, \
+ .src_clk = sc, \
+ .md_val = m_v, \
+ .ns_val = n_v, \
+ .ctl_val = c_v, \
+ .mnd_en_mask = m_m, \
+ .sys_vdd = v, \
+ .extra_freq_data = e, \
+ }
+#define FREQ_END (UINT_MAX-1)
+#define F_END \
+ { \
+ .freq_hz = FREQ_END, \
+ .sys_vdd = LOW, \
+ }
+
+/**
+ * struct branch - branch on/off
+ * @ctl_reg: clock control register
+ * @en_mask: ORed with @ctl_reg to enable the clock
+ * @halt_reg: halt register
+ * @halt_check: type of halt check to perform
+ * @halt_bit: ANDed with @halt_reg to test for clock halted
+ * @reset_reg: reset register
+ * @reset_mask: ORed with @reset_reg to reset the clock domain
+ */
+struct branch {
+ void __iomem *const ctl_reg;
+ const u32 en_mask;
+
+ void __iomem *const halt_reg;
+ const u16 halt_check;
+ const u16 halt_bit;
+
+ void __iomem *const reset_reg;
+ const u32 reset_mask;
+};
+
+int branch_reset(struct branch *clk, enum clk_reset_action action);
+
+/*
+ * Generic clock-definition struct and macros
+ */
+struct rcg_clk {
+ bool enabled;
+ struct branch b;
+ void *const ns_reg;
+ void *const md_reg;
+ const uint32_t root_en_mask;
+ uint32_t ns_mask;
+ const uint32_t ctl_mask;
+ struct clk *depends;
+ void (*set_rate)(struct clk_local *, struct clk_freq_tbl *);
+ struct clk_freq_tbl *const freq_tbl;
+ struct clk_freq_tbl *current_freq;
+
+ struct clk c;
+};
+
+static inline struct rcg_clk *to_rcg_clk(struct clk *clk)
+{
+ return container_of(clk, struct rcg_clk, c);
+}
+
+int rcg_clk_enable(struct clk *clk);
+void rcg_clk_disable(struct clk *clk);
+void rcg_clk_auto_off(struct clk *clk);
+int rcg_clk_set_rate(struct clk *clk, unsigned rate);
+int rcg_clk_set_min_rate(struct clk *clk, unsigned rate);
+int rcg_clk_set_max_rate(struct clk *clk, unsigned rate);
+unsigned rcg_clk_get_rate(struct clk *clk);
+unsigned rcg_clk_is_enabled(struct clk *clk);
+long rcg_clk_round_rate(struct clk *clk, unsigned rate);
+struct clk *rcg_clk_get_parent(struct clk *c);
+
+/*
+ * SYS_VDD voltage levels
+ */
+enum sys_vdd_level {
+ NONE,
+ LOW,
+ NOMINAL,
+ HIGH,
+ NUM_SYS_VDD_LEVELS
+};
+
+/**
+ * struct fixed_clk - fixed rate clock (used for crystal oscillators)
+ * @rate: output rate
+ * @c: clk
+ */
+struct fixed_clk {
+ unsigned long rate;
+ struct clk c;
+};
+
+static inline struct fixed_clk *to_fixed_clk(struct clk *clk)
+{
+ return container_of(clk, struct fixed_clk, c);
+}
+
+static inline unsigned fixed_clk_get_rate(struct clk *clk)
+{
+ struct fixed_clk *f = to_fixed_clk(clk);
+ return f->rate;
+}
+
+
+/**
+ * struct pll_vote_clk - phase locked loop (HW voteable)
+ * @rate: output rate
+ * @en_reg: enable register
+ * @en_mask: ORed with @en_reg to enable the clock
+ * @status_reg: status register
+ * @parent: clock source
+ * @c: clk
+ */
+struct pll_vote_clk {
+ unsigned long rate;
+
+ void __iomem *const en_reg;
+ const u32 en_mask;
+
+ void __iomem *const status_reg;
+
+ struct clk *parent;
+ struct clk c;
+};
+
+extern struct clk_ops clk_ops_pll_vote;
+
+static inline struct pll_vote_clk *to_pll_vote_clk(struct clk *clk)
+{
+ return container_of(clk, struct pll_vote_clk, c);
+}
+
+/**
+ * struct branch_clk - branch
+ * @enabled: true if clock is on, false otherwise
+ * @b: branch
+ * @parent: clock source
+ * @c: clk
+ *
+ * An on/off switch with a rate derived from the parent.
+ */
+struct branch_clk {
+ bool enabled;
+ struct branch b;
+ struct clk *parent;
+ struct clk *depends;
+ struct clk c;
+};
+
+static inline struct branch_clk *to_branch_clk(struct clk *clk)
+{
+ return container_of(clk, struct branch_clk, c);
+}
+
+int branch_clk_enable(struct clk *clk);
+void branch_clk_disable(struct clk *clk);
+struct clk *branch_clk_get_parent(struct clk *clk);
+int branch_clk_set_parent(struct clk *clk, struct clk *parent);
+unsigned branch_clk_is_enabled(struct clk *clk);
+void branch_clk_auto_off(struct clk *clk);
+int branch_clk_reset(struct clk *c, enum clk_reset_action action);
+
+/*
+ * Variables from clock-local driver
+ */
+extern spinlock_t local_clock_reg_lock;
+extern struct clk_freq_tbl local_dummy_freq;
+extern struct fixed_clk gnd_clk;
+
+/*
+ * Local-clock APIs
+ */
+int local_vote_sys_vdd(enum sys_vdd_level level);
+int local_unvote_sys_vdd(enum sys_vdd_level level);
+bool local_clk_is_local(struct clk *clk);
+
+/*
+ * Required SoC-specific functions, implemented for every supported SoC
+ */
+extern int (*soc_update_sys_vdd)(enum sys_vdd_level level);
+
+/*
+ * Generic set-rate implementations
+ */
+void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf);
+void set_rate_nop(struct rcg_clk *clk, struct clk_freq_tbl *nf);
+
+#endif /* __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H */
+
--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
WARNING: multiple messages have this Message-ID (diff)
From: davidb@codeaurora.org (David Brown)
To: linux-arm-kernel@lists.infradead.org
Subject: [RFC PATCH 15/34] msm: clock: Add local clock control framework
Date: Wed, 2 Nov 2011 11:36:12 -0700 [thread overview]
Message-ID: <1320258991-22325-16-git-send-email-davidb@codeaurora.org> (raw)
In-Reply-To: <1320258991-22325-1-git-send-email-davidb@codeaurora.org>
From: Stephen Boyd <sboyd@codeaurora.org>
Based on code originally written by Saravana Kannan, rewritten by
Matt Wagantall.
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: David Brown <davidb@codeaurora.org>
---
arch/arm/mach-msm/clock-local.c | 700 +++++++++++++++++++++++++++++++++++++++
arch/arm/mach-msm/clock-local.h | 242 ++++++++++++++
2 files changed, 942 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-msm/clock-local.c
create mode 100644 arch/arm/mach-msm/clock-local.h
diff --git a/arch/arm/mach-msm/clock-local.c b/arch/arm/mach-msm/clock-local.c
new file mode 100644
index 0000000..7a6dc5d
--- /dev/null
+++ b/arch/arm/mach-msm/clock-local.c
@@ -0,0 +1,700 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. 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/kernel.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/ctype.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <mach/clk.h>
+
+#include "clock.h"
+#include "clock-local.h"
+
+/*
+ * When enabling/disabling a clock, check the halt bit up to this number
+ * number of times (with a 1 us delay in between) before continuing.
+ */
+#define HALT_CHECK_MAX_LOOPS 100
+/* For clock without halt checking, wait this long after enables/disables. */
+#define HALT_CHECK_DELAY_US 10
+
+DEFINE_SPINLOCK(local_clock_reg_lock);
+struct clk_freq_tbl local_dummy_freq = F_END;
+
+unsigned local_sys_vdd_votes[NUM_SYS_VDD_LEVELS];
+static DEFINE_SPINLOCK(sys_vdd_vote_lock);
+
+/*
+ * Common Set-Rate Functions
+ */
+
+/* For clocks with MND dividers. */
+void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf)
+{
+ u32 ns_reg_val, ctl_reg_val;
+
+ /* Assert MND reset. */
+ ns_reg_val = readl_relaxed(clk->ns_reg);
+ ns_reg_val |= BIT(7);
+ writel_relaxed(ns_reg_val, clk->ns_reg);
+
+ /* Program M and D values. */
+ writel_relaxed(nf->md_val, clk->md_reg);
+
+ /* If the clock has a separate CC register, program it. */
+ if (clk->ns_reg != clk->b.ctl_reg) {
+ ctl_reg_val = readl_relaxed(clk->b.ctl_reg);
+ ctl_reg_val &= ~(clk->ctl_mask);
+ ctl_reg_val |= nf->ctl_val;
+ writel_relaxed(ctl_reg_val, clk->b.ctl_reg);
+ }
+
+ /* Deassert MND reset. */
+ ns_reg_val &= ~BIT(7);
+ writel_relaxed(ns_reg_val, clk->ns_reg);
+}
+
+void set_rate_nop(struct rcg_clk *clk, struct clk_freq_tbl *nf)
+{
+ /*
+ * Nothing to do for fixed-rate or integer-divider clocks. Any settings
+ * in NS registers are applied in the enable path, since power can be
+ * saved by leaving an un-clocked or slowly-clocked source selected
+ * until the clock is enabled.
+ */
+}
+
+int (*soc_update_sys_vdd)(enum sys_vdd_level level);
+/*
+ * SYS_VDD voting functions
+ */
+
+/* Update system voltage level given the current votes. */
+static int local_update_sys_vdd(void)
+{
+ static int cur_level = NUM_SYS_VDD_LEVELS;
+ int level, rc = 0;
+
+ if (local_sys_vdd_votes[HIGH])
+ level = HIGH;
+ else if (local_sys_vdd_votes[NOMINAL])
+ level = NOMINAL;
+ else if (local_sys_vdd_votes[LOW])
+ level = LOW;
+ else
+ level = NONE;
+
+ if (level == cur_level)
+ return rc;
+
+ rc = soc_update_sys_vdd(level);
+ if (!rc)
+ cur_level = level;
+
+ return rc;
+}
+
+/* Vote for a system voltage level. */
+int local_vote_sys_vdd(unsigned level)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ /* Bounds checking. */
+ if (level >= ARRAY_SIZE(local_sys_vdd_votes))
+ return -EINVAL;
+
+ spin_lock_irqsave(&sys_vdd_vote_lock, flags);
+ local_sys_vdd_votes[level]++;
+ rc = local_update_sys_vdd();
+ if (rc)
+ local_sys_vdd_votes[level]--;
+ spin_unlock_irqrestore(&sys_vdd_vote_lock, flags);
+
+ return rc;
+}
+
+/* Remove vote for a system voltage level. */
+int local_unvote_sys_vdd(unsigned level)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ /* Bounds checking. */
+ if (level >= ARRAY_SIZE(local_sys_vdd_votes))
+ return -EINVAL;
+
+ spin_lock_irqsave(&sys_vdd_vote_lock, flags);
+
+ if (WARN(!local_sys_vdd_votes[level],
+ "Reference counts are incorrect for level %d!\n", level))
+ goto out;
+
+ local_sys_vdd_votes[level]--;
+ rc = local_update_sys_vdd();
+ if (rc)
+ local_sys_vdd_votes[level]++;
+out:
+ spin_unlock_irqrestore(&sys_vdd_vote_lock, flags);
+ return rc;
+}
+/*
+ * Clock enable/disable functions
+ */
+
+/* Return non-zero if a clock status registers shows the clock is halted. */
+static int branch_clk_is_halted(const struct branch *clk)
+{
+ int invert = (clk->halt_check == ENABLE);
+ int status_bit = readl_relaxed(clk->halt_reg) & BIT(clk->halt_bit);
+ return invert ? !status_bit : status_bit;
+}
+
+static void __branch_clk_enable_reg(const struct branch *clk, const char *name)
+{
+ u32 reg_val;
+
+ if (clk->en_mask) {
+ reg_val = readl_relaxed(clk->ctl_reg);
+ reg_val |= clk->en_mask;
+ writel_relaxed(reg_val, clk->ctl_reg);
+ }
+
+ /*
+ * Use a memory barrier since some halt status registers are
+ * not within the same 1K segment as the branch/root enable
+ * registers. It's also needed in the udelay() case to ensure
+ * the delay starts after the branch enable.
+ */
+ mb();
+
+ /* Wait for clock to enable before returning. */
+ if (clk->halt_check == DELAY)
+ udelay(HALT_CHECK_DELAY_US);
+ else if (clk->halt_check == ENABLE || clk->halt_check == HALT
+ || clk->halt_check == ENABLE_VOTED
+ || clk->halt_check == HALT_VOTED) {
+ int count;
+
+ /* Wait up to HALT_CHECK_MAX_LOOPS for clock to enable. */
+ for (count = HALT_CHECK_MAX_LOOPS; branch_clk_is_halted(clk)
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "%s status stuck at 'off'", name);
+ }
+}
+
+/* Perform any register operations required to enable the clock. */
+static void __rcg_clk_enable_reg(struct rcg_clk *clk)
+{
+ u32 reg_val;
+ void __iomem *const reg = clk->b.ctl_reg;
+
+ WARN(clk->current_freq == &local_dummy_freq,
+ "Attempting to enable %s before setting its rate. "
+ "Set the rate first!\n", clk->c.dbg_name);
+
+ /*
+ * Program the NS register, if applicable. NS registers are not
+ * set in the set_rate path because power can be saved by deferring
+ * the selection of a clocked source until the clock is enabled.
+ */
+ if (clk->ns_mask) {
+ reg_val = readl_relaxed(clk->ns_reg);
+ reg_val &= ~(clk->ns_mask);
+ reg_val |= (clk->current_freq->ns_val & clk->ns_mask);
+ writel_relaxed(reg_val, clk->ns_reg);
+ }
+
+ /* Enable MN counter, if applicable. */
+ reg_val = readl_relaxed(reg);
+ if (clk->current_freq->mnd_en_mask) {
+ reg_val |= clk->current_freq->mnd_en_mask;
+ writel_relaxed(reg_val, reg);
+ }
+ /* Enable root. */
+ if (clk->root_en_mask) {
+ reg_val |= clk->root_en_mask;
+ writel_relaxed(reg_val, reg);
+ }
+ __branch_clk_enable_reg(&clk->b, clk->c.dbg_name);
+}
+
+/* Perform any register operations required to disable the branch. */
+static u32 __branch_clk_disable_reg(const struct branch *clk, const char *name)
+{
+ u32 reg_val;
+
+ reg_val = readl_relaxed(clk->ctl_reg);
+ if (clk->en_mask) {
+ reg_val &= ~(clk->en_mask);
+ writel_relaxed(reg_val, clk->ctl_reg);
+ }
+
+ /*
+ * Use a memory barrier since some halt status registers are
+ * not within the same K segment as the branch/root enable
+ * registers. It's also needed in the udelay() case to ensure
+ * the delay starts after the branch disable.
+ */
+ mb();
+
+ /* Wait for clock to disable before continuing. */
+ if (clk->halt_check == DELAY || clk->halt_check == ENABLE_VOTED
+ || clk->halt_check == HALT_VOTED)
+ udelay(HALT_CHECK_DELAY_US);
+ else if (clk->halt_check == ENABLE || clk->halt_check == HALT) {
+ int count;
+
+ /* Wait up to HALT_CHECK_MAX_LOOPS for clock to disable. */
+ for (count = HALT_CHECK_MAX_LOOPS; !branch_clk_is_halted(clk)
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "%s status stuck at 'on'", name);
+ }
+
+ return reg_val;
+}
+
+/* Perform any register operations required to disable the generator. */
+static void __rcg_clk_disable_reg(struct rcg_clk *clk)
+{
+ void __iomem *const reg = clk->b.ctl_reg;
+ u32 reg_val;
+
+ reg_val = __branch_clk_disable_reg(&clk->b, clk->c.dbg_name);
+ /* Disable root. */
+ if (clk->root_en_mask) {
+ reg_val &= ~(clk->root_en_mask);
+ writel_relaxed(reg_val, reg);
+ }
+ /* Disable MN counter, if applicable. */
+ if (clk->current_freq->mnd_en_mask) {
+ reg_val &= ~(clk->current_freq->mnd_en_mask);
+ writel_relaxed(reg_val, reg);
+ }
+ /*
+ * Program NS register to low-power value with an un-clocked or
+ * slowly-clocked source selected.
+ */
+ if (clk->ns_mask) {
+ reg_val = readl_relaxed(clk->ns_reg);
+ reg_val &= ~(clk->ns_mask);
+ reg_val |= (clk->freq_tbl->ns_val & clk->ns_mask);
+ writel_relaxed(reg_val, clk->ns_reg);
+ }
+}
+
+static void _rcg_clk_enable(struct rcg_clk *clk)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __rcg_clk_enable_reg(clk);
+ clk->enabled = true;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+static void _rcg_clk_disable(struct rcg_clk *clk)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __rcg_clk_disable_reg(clk);
+ clk->enabled = false;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+/* Enable a clock and any related power rail. */
+int rcg_clk_enable(struct clk *c)
+{
+ int rc;
+ struct rcg_clk *clk = to_rcg_clk(c);
+
+ rc = local_vote_sys_vdd(clk->current_freq->sys_vdd);
+ if (rc)
+ goto err_vdd;
+ rc = clk_enable(clk->depends);
+ if (rc)
+ goto err_dep;
+ _rcg_clk_enable(clk);
+ return rc;
+
+err_dep:
+ local_unvote_sys_vdd(clk->current_freq->sys_vdd);
+err_vdd:
+ return rc;
+}
+
+/* Disable a clock and any related power rail. */
+void rcg_clk_disable(struct clk *c)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+
+ _rcg_clk_disable(clk);
+ clk_disable(clk->depends);
+ local_unvote_sys_vdd(clk->current_freq->sys_vdd);
+}
+
+/* Turn off a clock at boot, without checking refcounts or disabling depends. */
+void rcg_clk_auto_off(struct clk *c)
+{
+ _rcg_clk_disable(to_rcg_clk(c));
+}
+
+/*
+ * Frequency-related functions
+ */
+
+/* Set a clock's frequency. */
+static int _rcg_clk_set_rate(struct rcg_clk *clk, struct clk_freq_tbl *nf)
+{
+ struct clk_freq_tbl *cf;
+ int rc = 0;
+ struct clk *chld;
+ unsigned long flags;
+
+ spin_lock_irqsave(&clk->c.lock, flags);
+
+ /* Check if frequency is actually changed. */
+ cf = clk->current_freq;
+ if (nf == cf)
+ goto unlock;
+
+ if (clk->enabled) {
+ /* Vote for voltage and source for new freq. */
+ rc = local_vote_sys_vdd(nf->sys_vdd);
+ if (rc)
+ goto unlock;
+ rc = clk_enable(nf->src_clk);
+ if (rc) {
+ local_unvote_sys_vdd(nf->sys_vdd);
+ goto unlock;
+ }
+ }
+
+ spin_lock(&local_clock_reg_lock);
+
+ /* Disable all branches to prevent glitches. */
+ list_for_each_entry(chld, &clk->c.children, siblings) {
+ struct branch_clk *x = to_branch_clk(chld);
+ /* Don't bother turning off if it is already off.
+ * Checking ch->enabled is cheaper (cache) than reading
+ * and writing to a register (uncached/unbuffered). */
+ if (x->enabled)
+ __branch_clk_disable_reg(&x->b, x->c.dbg_name);
+ }
+ if (clk->enabled)
+ __rcg_clk_disable_reg(clk);
+
+ /* Perform clock-specific frequency switch operations. */
+ BUG_ON(!clk->set_rate);
+ clk->set_rate(clk, nf);
+
+ /*
+ * Current freq must be updated before __rcg_clk_enable_reg()
+ * is called to make sure the MNCNTR_EN bit is set correctly.
+ */
+ clk->current_freq = nf;
+
+ if (clk->enabled)
+ __rcg_clk_enable_reg(clk);
+ /* Enable only branches that were ON before. */
+ list_for_each_entry(chld, &clk->c.children, siblings) {
+ struct branch_clk *x = to_branch_clk(chld);
+ if (x->enabled)
+ __branch_clk_enable_reg(&x->b, x->c.dbg_name);
+ }
+
+ spin_unlock(&local_clock_reg_lock);
+
+ /* Release requirements of the old freq. */
+ if (clk->enabled) {
+ clk_disable(cf->src_clk);
+ local_unvote_sys_vdd(cf->sys_vdd);
+ }
+unlock:
+ spin_unlock_irqrestore(&clk->c.lock, flags);
+
+ return rc;
+}
+
+/* Set a clock to an exact rate. */
+int rcg_clk_set_rate(struct clk *c, unsigned rate)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ struct clk_freq_tbl *nf;
+
+ for (nf = clk->freq_tbl; nf->freq_hz != FREQ_END
+ && nf->freq_hz != rate; nf++)
+ ;
+
+ if (nf->freq_hz == FREQ_END)
+ return -EINVAL;
+
+ return _rcg_clk_set_rate(clk, nf);
+}
+
+/* Set a clock to a rate greater than some minimum. */
+int rcg_clk_set_min_rate(struct clk *c, unsigned rate)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ struct clk_freq_tbl *nf;
+
+ for (nf = clk->freq_tbl; nf->freq_hz != FREQ_END
+ && nf->freq_hz < rate; nf++)
+ ;
+
+ if (nf->freq_hz == FREQ_END)
+ return -EINVAL;
+
+ return _rcg_clk_set_rate(clk, nf);
+}
+
+/* Set a clock to a maximum rate. */
+int rcg_clk_set_max_rate(struct clk *clk, unsigned rate)
+{
+ return -EPERM;
+}
+
+/* Get the currently-set rate of a clock in Hz. */
+unsigned rcg_clk_get_rate(struct clk *c)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ unsigned long flags;
+ unsigned ret = 0;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ ret = clk->current_freq->freq_hz;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ /*
+ * Return 0 if the rate has never been set. Might not be correct,
+ * but it's good enough.
+ */
+ if (ret == FREQ_END)
+ ret = 0;
+
+ return ret;
+}
+
+/* Check if a clock is currently enabled. */
+unsigned rcg_clk_is_enabled(struct clk *clk)
+{
+ return to_rcg_clk(clk)->enabled;
+}
+
+/* Return a supported rate that's at least the specified rate. */
+long rcg_clk_round_rate(struct clk *c, unsigned rate)
+{
+ struct rcg_clk *clk = to_rcg_clk(c);
+ struct clk_freq_tbl *f;
+
+ for (f = clk->freq_tbl; f->freq_hz != FREQ_END; f++)
+ if (f->freq_hz >= rate)
+ return f->freq_hz;
+
+ return -EPERM;
+}
+
+bool local_clk_is_local(struct clk *clk)
+{
+ return true;
+}
+
+struct clk *rcg_clk_get_parent(struct clk *clk)
+{
+ return to_rcg_clk(clk)->current_freq->src_clk;
+}
+
+static int pll_vote_clk_enable(struct clk *clk)
+{
+ u32 ena;
+ unsigned long flags;
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ ena = readl_relaxed(pll->en_reg);
+ ena |= pll->en_mask;
+ writel_relaxed(ena, pll->en_reg);
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ /* Wait until PLL is enabled */
+ while ((readl_relaxed(pll->status_reg) & BIT(16)) == 0)
+ cpu_relax();
+
+ return 0;
+}
+
+static void pll_vote_clk_disable(struct clk *clk)
+{
+ u32 ena;
+ unsigned long flags;
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ ena = readl_relaxed(pll->en_reg);
+ ena &= ~(pll->en_mask);
+ writel_relaxed(ena, pll->en_reg);
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+static unsigned pll_vote_clk_get_rate(struct clk *clk)
+{
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+ return pll->rate;
+}
+
+static struct clk *pll_vote_clk_get_parent(struct clk *clk)
+{
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+ return pll->parent;
+}
+
+static unsigned pll_vote_clk_is_enabled(struct clk *clk)
+{
+ struct pll_vote_clk *pll = to_pll_vote_clk(clk);
+ return !!(readl_relaxed(pll->status_reg) & BIT(16));
+}
+
+struct clk_ops clk_ops_pll_vote = {
+ .enable = pll_vote_clk_enable,
+ .disable = pll_vote_clk_disable,
+ .is_enabled = pll_vote_clk_is_enabled,
+ .get_rate = pll_vote_clk_get_rate,
+ .get_parent = pll_vote_clk_get_parent,
+ .is_local = local_clk_is_local,
+};
+
+struct clk_ops clk_ops_gnd = {
+ .get_rate = fixed_clk_get_rate,
+ .is_local = local_clk_is_local,
+};
+
+struct fixed_clk gnd_clk = {
+ .c = {
+ .dbg_name = "ground_clk",
+ .ops = &clk_ops_gnd,
+ CLK_INIT(gnd_clk.c),
+ },
+};
+
+int branch_clk_enable(struct clk *clk)
+{
+ int rc;
+ unsigned long flags;
+ struct branch_clk *branch = to_branch_clk(clk);
+
+ rc = clk_enable(branch->depends);
+ if (rc)
+ return rc;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __branch_clk_enable_reg(&branch->b, branch->c.dbg_name);
+ branch->enabled = true;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ return 0;
+}
+
+void branch_clk_disable(struct clk *clk)
+{
+ unsigned long flags;
+ struct branch_clk *branch = to_branch_clk(clk);
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __branch_clk_disable_reg(&branch->b, branch->c.dbg_name);
+ branch->enabled = false;
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ clk_disable(branch->depends);
+}
+
+struct clk *branch_clk_get_parent(struct clk *clk)
+{
+ struct branch_clk *branch = to_branch_clk(clk);
+ return branch->parent;
+}
+
+int branch_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ /*
+ * We setup the parent pointer at init time in msm_clock_init().
+ * This check is to make sure drivers can't change the parent.
+ */
+ if (parent && list_empty(&clk->siblings)) {
+ list_add(&clk->siblings, &parent->children);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+unsigned branch_clk_is_enabled(struct clk *clk)
+{
+ struct branch_clk *branch = to_branch_clk(clk);
+ return branch->enabled;
+}
+
+void branch_clk_auto_off(struct clk *clk)
+{
+ struct branch_clk *branch = to_branch_clk(clk);
+ unsigned long flags;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+ __branch_clk_disable_reg(&branch->b, branch->c.dbg_name);
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+int branch_reset(struct branch *clk, enum clk_reset_action action)
+{
+ int ret = 0;
+ u32 reg_val;
+ unsigned long flags;
+
+ if (!clk->reset_reg)
+ return -EPERM;
+
+ spin_lock_irqsave(&local_clock_reg_lock, flags);
+
+ reg_val = readl_relaxed(clk->reset_reg);
+ switch (action) {
+ case CLK_RESET_ASSERT:
+ reg_val |= clk->reset_mask;
+ break;
+ case CLK_RESET_DEASSERT:
+ reg_val &= ~(clk->reset_mask);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ writel_relaxed(reg_val, clk->reset_reg);
+
+ spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+ /* Make sure write is issued before returning. */
+ mb();
+
+ return ret;
+}
+
+int branch_clk_reset(struct clk *clk, enum clk_reset_action action)
+{
+ return branch_reset(&to_branch_clk(clk)->b, action);
+}
diff --git a/arch/arm/mach-msm/clock-local.h b/arch/arm/mach-msm/clock-local.h
new file mode 100644
index 0000000..a7c9001
--- /dev/null
+++ b/arch/arm/mach-msm/clock-local.h
@@ -0,0 +1,242 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. 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.
+ */
+
+#ifndef __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H
+#define __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H
+
+#include <linux/spinlock.h>
+#include "clock.h"
+
+/*
+ * Bit manipulation macros
+ */
+#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb)
+#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb))
+
+/*
+ * Halt/Status Checking Mode Macros
+ */
+#define HALT 0 /* Bit pol: 1 = halted */
+#define NOCHECK 1 /* No bit to check, do nothing */
+#define HALT_VOTED 2 /* Bit pol: 1 = halted; delay on disable */
+#define ENABLE 3 /* Bit pol: 1 = running */
+#define ENABLE_VOTED 4 /* Bit pol: 1 = running; delay on disable */
+#define DELAY 5 /* No bit to check, just delay */
+
+/*
+ * Generic frequency-definition structs and macros
+ */
+struct clk_freq_tbl {
+ const uint32_t freq_hz;
+ struct clk *src_clk;
+ const uint32_t md_val;
+ const uint32_t ns_val;
+ const uint32_t ctl_val;
+ uint32_t mnd_en_mask;
+ const unsigned sys_vdd;
+ void *const extra_freq_data;
+};
+
+#define F_RAW(f, sc, m_v, n_v, c_v, m_m, v, e) { \
+ .freq_hz = f, \
+ .src_clk = sc, \
+ .md_val = m_v, \
+ .ns_val = n_v, \
+ .ctl_val = c_v, \
+ .mnd_en_mask = m_m, \
+ .sys_vdd = v, \
+ .extra_freq_data = e, \
+ }
+#define FREQ_END (UINT_MAX-1)
+#define F_END \
+ { \
+ .freq_hz = FREQ_END, \
+ .sys_vdd = LOW, \
+ }
+
+/**
+ * struct branch - branch on/off
+ * @ctl_reg: clock control register
+ * @en_mask: ORed with @ctl_reg to enable the clock
+ * @halt_reg: halt register
+ * @halt_check: type of halt check to perform
+ * @halt_bit: ANDed with @halt_reg to test for clock halted
+ * @reset_reg: reset register
+ * @reset_mask: ORed with @reset_reg to reset the clock domain
+ */
+struct branch {
+ void __iomem *const ctl_reg;
+ const u32 en_mask;
+
+ void __iomem *const halt_reg;
+ const u16 halt_check;
+ const u16 halt_bit;
+
+ void __iomem *const reset_reg;
+ const u32 reset_mask;
+};
+
+int branch_reset(struct branch *clk, enum clk_reset_action action);
+
+/*
+ * Generic clock-definition struct and macros
+ */
+struct rcg_clk {
+ bool enabled;
+ struct branch b;
+ void *const ns_reg;
+ void *const md_reg;
+ const uint32_t root_en_mask;
+ uint32_t ns_mask;
+ const uint32_t ctl_mask;
+ struct clk *depends;
+ void (*set_rate)(struct clk_local *, struct clk_freq_tbl *);
+ struct clk_freq_tbl *const freq_tbl;
+ struct clk_freq_tbl *current_freq;
+
+ struct clk c;
+};
+
+static inline struct rcg_clk *to_rcg_clk(struct clk *clk)
+{
+ return container_of(clk, struct rcg_clk, c);
+}
+
+int rcg_clk_enable(struct clk *clk);
+void rcg_clk_disable(struct clk *clk);
+void rcg_clk_auto_off(struct clk *clk);
+int rcg_clk_set_rate(struct clk *clk, unsigned rate);
+int rcg_clk_set_min_rate(struct clk *clk, unsigned rate);
+int rcg_clk_set_max_rate(struct clk *clk, unsigned rate);
+unsigned rcg_clk_get_rate(struct clk *clk);
+unsigned rcg_clk_is_enabled(struct clk *clk);
+long rcg_clk_round_rate(struct clk *clk, unsigned rate);
+struct clk *rcg_clk_get_parent(struct clk *c);
+
+/*
+ * SYS_VDD voltage levels
+ */
+enum sys_vdd_level {
+ NONE,
+ LOW,
+ NOMINAL,
+ HIGH,
+ NUM_SYS_VDD_LEVELS
+};
+
+/**
+ * struct fixed_clk - fixed rate clock (used for crystal oscillators)
+ * @rate: output rate
+ * @c: clk
+ */
+struct fixed_clk {
+ unsigned long rate;
+ struct clk c;
+};
+
+static inline struct fixed_clk *to_fixed_clk(struct clk *clk)
+{
+ return container_of(clk, struct fixed_clk, c);
+}
+
+static inline unsigned fixed_clk_get_rate(struct clk *clk)
+{
+ struct fixed_clk *f = to_fixed_clk(clk);
+ return f->rate;
+}
+
+
+/**
+ * struct pll_vote_clk - phase locked loop (HW voteable)
+ * @rate: output rate
+ * @en_reg: enable register
+ * @en_mask: ORed with @en_reg to enable the clock
+ * @status_reg: status register
+ * @parent: clock source
+ * @c: clk
+ */
+struct pll_vote_clk {
+ unsigned long rate;
+
+ void __iomem *const en_reg;
+ const u32 en_mask;
+
+ void __iomem *const status_reg;
+
+ struct clk *parent;
+ struct clk c;
+};
+
+extern struct clk_ops clk_ops_pll_vote;
+
+static inline struct pll_vote_clk *to_pll_vote_clk(struct clk *clk)
+{
+ return container_of(clk, struct pll_vote_clk, c);
+}
+
+/**
+ * struct branch_clk - branch
+ * @enabled: true if clock is on, false otherwise
+ * @b: branch
+ * @parent: clock source
+ * @c: clk
+ *
+ * An on/off switch with a rate derived from the parent.
+ */
+struct branch_clk {
+ bool enabled;
+ struct branch b;
+ struct clk *parent;
+ struct clk *depends;
+ struct clk c;
+};
+
+static inline struct branch_clk *to_branch_clk(struct clk *clk)
+{
+ return container_of(clk, struct branch_clk, c);
+}
+
+int branch_clk_enable(struct clk *clk);
+void branch_clk_disable(struct clk *clk);
+struct clk *branch_clk_get_parent(struct clk *clk);
+int branch_clk_set_parent(struct clk *clk, struct clk *parent);
+unsigned branch_clk_is_enabled(struct clk *clk);
+void branch_clk_auto_off(struct clk *clk);
+int branch_clk_reset(struct clk *c, enum clk_reset_action action);
+
+/*
+ * Variables from clock-local driver
+ */
+extern spinlock_t local_clock_reg_lock;
+extern struct clk_freq_tbl local_dummy_freq;
+extern struct fixed_clk gnd_clk;
+
+/*
+ * Local-clock APIs
+ */
+int local_vote_sys_vdd(enum sys_vdd_level level);
+int local_unvote_sys_vdd(enum sys_vdd_level level);
+bool local_clk_is_local(struct clk *clk);
+
+/*
+ * Required SoC-specific functions, implemented for every supported SoC
+ */
+extern int (*soc_update_sys_vdd)(enum sys_vdd_level level);
+
+/*
+ * Generic set-rate implementations
+ */
+void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf);
+void set_rate_nop(struct rcg_clk *clk, struct clk_freq_tbl *nf);
+
+#endif /* __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H */
+
--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
next prev parent reply other threads:[~2011-11-02 18:37 UTC|newest]
Thread overview: 80+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-11-02 18:35 [RFC PATCH 00/34] msm: msm8660 and msm8960 clock support David Brown
2011-11-02 18:35 ` David Brown
2011-11-02 18:35 ` [RFC PATCH 01/34] msm: clock-pcom: Mark functions static David Brown
2011-11-02 18:35 ` David Brown
2011-11-02 18:35 ` [RFC PATCH 02/34] msm: clock: Always use an array to iterate over clocks David Brown
2011-11-02 18:35 ` David Brown
2011-11-02 18:35 ` David Brown
2011-11-02 19:45 ` Russell King - ARM Linux
2011-11-02 19:45 ` Russell King - ARM Linux
2011-11-02 21:34 ` Stephen Boyd
2011-11-02 21:34 ` Stephen Boyd
2011-11-02 18:36 ` [RFC PATCH 03/34] msm: clock: Pass struct clk to the clk_ops David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 04/34] msm: clock: Support one lock per clock David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 05/34] msm: clock-pcom: Introduce a struct pcom_clk David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 06/34] msm: clock: Support clk_[s|g]et_parent() clk_ops David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 07/34] msm: clock-debug: Use clk_enable()/clk_disable() directly David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 08/34] msm: clock: Enable/disable parent clocks generically David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 09/34] msm: clock: Implement rate voting David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 10/34] msm: clock-pcom: Add pbus specific clock ops David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 11/34] msm: Migrate to clock rate voting David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 12/34] msm: clock: Make most clk_*() operations optional David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 13/34] msm: clock-debug: Implement a default is_enabled() David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 14/34] msm: proc_comm: Add CLKCTL_RPC_SRC_REQUEST David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown [this message]
2011-11-02 18:36 ` [RFC PATCH 15/34] msm: clock: Add local clock control framework David Brown
2011-11-02 18:36 ` [RFC PATCH 16/34] msm: clock-pcom: Expose pc_clk_reset David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 17/34] msm: clock: Add 7x30 local clock support David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 18/34] msm: clock-local: Add support for 8x60 clock types David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 19/34] msm: clock: Add 8x60 clock support David Brown
2011-11-02 18:36 ` [RFC PATCH 20/34] msm: clock: Add list_rate debugfs nodes for locally-controlled clocks David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 21/34] msm: clock: Add debugfs interface to measure clock rates David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 22/34] msm: clock-8x60: Support measurement of CPU and L2 clocks David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 23/34] msm: Unify iomap for clock regions David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 24/34] msm: clock: Support dummy clocks David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 25/34] msm: clock: Add 8960 clock support David Brown
2011-11-02 18:36 ` [RFC PATCH 26/34] msm: 8660: Add FLUID support David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 27/34] msm-8x60: Add serial support David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 28/34] msm: clock: Invert CLKFLAG_AUTO_OFF David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 29/34] msm: clock: Expand CLK_MIN, CLK_MAX and CLK_MINMAX macros David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 30/34] msm: clock: Add EBI1 voter clocks for ADM on SoCs without them David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 31/34] msm: clock: Remove msm_clk_soc_init() David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 32/34] msm: clock-8x60: Add local control of vpe_axi_clk and vpe_axi_clk David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 33/34] ARM: msm: fix names of UART clocks David Brown
2011-11-02 18:36 ` David Brown
2011-11-02 18:36 ` [RFC PATCH 34/34] msm_serial: fix clock rate on DMA-based uarts David Brown
2011-11-02 18:36 ` David Brown
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1320258991-22325-16-git-send-email-davidb@codeaurora.org \
--to=davidb@codeaurora.org \
--cc=bryanh@codeaurora.org \
--cc=dwalker@fifo99.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@arm.linux.org.uk \
--cc=sboyd@codeaurora.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.