diff mbox

[3/6] clk: tegra: add library for the DFLL clocksource (open-loop mode)

Message ID 20131219123701.3226.91986.stgit@tamien
State Superseded, archived
Headers show

Commit Message

Paul Walmsley Dec. 19, 2013, 12:37 p.m. UTC
Add shared code to support the Tegra DFLL clocksource in open-loop
mode.  This root clocksource is present on the Tegra114 and Tegra124
SoCs.  It's used to generate a clock for the fast CPU cluster.  Future
patches will add support for closed-loop mode.

This code was originally created by Aleksandr Frid <afrid@nvidia.com>.
It's been converted to use DT and integrated into the common clock
framework, and a few minor functional changes have been implemented.

Subsequent patches will add drivers for the Tegra114 and Tegra124 fast
CPU cluster DFLL devices, which rely on this code.

Signed-off-by: Paul Walmsley <pwalmsley@nvidia.com>
Cc: Aleksandr Frid <afrid@nvidia.com>
Cc: Matthew Longnecker <mlongnecker@nvidia.com>
Cc: Tezaswi Raja <traja@nvidia.com>
Cc: Peter De Schrijver <pdeschrijver@nvidia.com>
Cc: Prashant Gaikwad <pgaikwad@nvidia.com>
---
 drivers/clk/Kconfig          |    2 
 drivers/clk/tegra/Kconfig    |    9 
 drivers/clk/tegra/Makefile   |    8 
 drivers/clk/tegra/clk-dfll.c | 1229 ++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/tegra/clk-dfll.h |   54 ++
 5 files changed, 1302 insertions(+)
 create mode 100644 drivers/clk/tegra/Kconfig
 create mode 100644 drivers/clk/tegra/clk-dfll.c
 create mode 100644 drivers/clk/tegra/clk-dfll.h


--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Stephen Warren Dec. 19, 2013, 11:57 p.m. UTC | #1
On 12/19/2013 05:37 AM, Paul Walmsley wrote:
> Add shared code to support the Tegra DFLL clocksource in open-loop
> mode.  This root clocksource is present on the Tegra114 and Tegra124

"root clocksource" implies no parents, yet the clock object this driver
registers lists "dfll_soc" as its parent.

> SoCs.  It's used to generate a clock for the fast CPU cluster.  Future
> patches will add support for closed-loop mode.
> 
> This code was originally created by Aleksandr Frid <afrid@nvidia.com>.
> It's been converted to use DT and integrated into the common clock
> framework, and a few minor functional changes have been implemented.
> 
> Subsequent patches will add drivers for the Tegra114 and Tegra124 fast
> CPU cluster DFLL devices, which rely on this code.

> diff --git a/drivers/clk/tegra/Kconfig b/drivers/clk/tegra/Kconfig

> +config CLK_TEGRA_DFLL
> +	tristate
> +	depends on COMMON_CLK

Is there a need to make this optional? There's nothing configurable in
the rest of the Tegra clock driver, and it'd be nicer if the rest of the
kernel (e.g. Tegra cpufreq driver?) could simply always assume that DFLL
were available.

> +

Blank line at EOF. Same in Makefile.

> diff --git a/drivers/clk/tegra/Makefile b/drivers/clk/tegra/Makefile

> +# XXX -Wno-sign-compare is only needed due to problems with
> +# some non-clock-related Linux header files, and can be removed
> +# once those headers are fixed
> +CFLAGS_clk-dfll.o			+= -Wall -Wextra -Wno-unused-parameter \
> +					   -Wno-sign-compare

Aside from the no-sign-compare issue (which I assume is being fixed in
parallel before this is applied?) is there really a need to second-guess
the kernel's -W flags?

> diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c
> +/*
> + * clk-dfll.c - Tegra DFLL clock source common code

It'd be best not to put filenames in comments; it's just one more thing
to update if the file gets renamed.

> +struct tegra_dfll {
...
> +	u8				speedo_id;
> +	u8				process_id;

I don't think you need a copy of these; just read the globals directly?

> +static bool dfll_is_running(struct platform_device *pdev)
> +{
> +	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
> +
> +	return (td->mode == DFLL_DISABLED ||
> +		td->mode == DFLL_UNINITIALIZED) ? false : true;

Why not:

	return td->mode == DFLL_OPEN_LOOP;

or at least:

	return td->mode != DFLL_DISABLED &&
		td->mode != DFLL_UNINITIALIZED;

> +static void dfll_set_tune_range(struct platform_device *pdev,
> +				enum dfll_tune_range range)
> +{
> +	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
> +
> +	td->tune_range = range;

Do we really need a (private, single-use) function just to write a field
in a structure?

> +static u64  __attribute__((pure)) dfll_calc_monitored_rate(u32 monitor_data,

Does the compiler really need to be told about pure? It seems pretty
obvious from the code, and I imagine the compiler is smart enough to
tell. It looks like __pure would be preferred:

include/linux/compiler-gcc.h:94:#define __pure __attribute__((pure))

> +static u64 dfll_read_monitor_rate(struct platform_device *pdev)
...
> +	if (td->mode == DFLL_DISABLED || td->mode == DFLL_UNINITIALIZED)
> +		return 0;

if (!dfll_is_running(td))
	return 0;

> +static int dfll_init(struct platform_device *pdev)

> +	pm_runtime_enable(&pdev->dev);
> +	pm_runtime_get_sync(&pdev->dev);
> +
> +	td->ref_rate = clk_get_rate(td->ref_clk);

Doesn't clk_get_rate() work before clk_enable() (which happens in
pm_runtime_get_sync()), so you could simplify the error-handling here by
checking the rate a little earlier, outside the pm_runtime_get/put?

> +	if (td->ref_rate == 0) {
> +		dev_err(&pdev->dev, "ref_clk rate cannot be 0\n");
> +		ret = -EINVAL;
> +		goto di_err3;
> +	}
> +

> +	pm_runtime_put_sync(&pdev->dev);

Would plain pm_runtime_put() work just as well, and avoid the put if
something else enables the driver/dfll clock very soon after?

> +static const char *dfll_clk_parents[] = { "dfll_soc" };
> +
> +static struct clk_init_data dfll_clk_init_data = {
> +	.ops		= &dfll_clk_ops,
> +	.parent_names	= dfll_clk_parents,
> +	.num_parents	= ARRAY_SIZE(dfll_clk_parents),
> +};

Does a root clock actually have any parents?

> +static void dfll_unregister_clk(struct platform_device *pdev)
> +{
> +	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
> +
> +	/* XXX Why doesn't clk_unregister_clkdev() exist? */

Hmm. Seems like we need a patch to the clkdev core before this series?

> +static int dfll_enable_get(void *data, u64 *val)
> +{
> +	struct platform_device *pdev = data;
> +	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
> +
> +	*val = (td->mode == DFLL_OPEN_LOOP);

Use dfll_is_running()? Given td->mode embodies both enable/disable and
the current mode, would it make more sense to have a debugfs file that
exposed the raw enum, so you could both tell is-enabled and the current
mode?

> +
> +	return 0;
> +}
> +static int dfll_enable_set(void *data, u64 val)

Missing blank line.

> +{
> +	struct platform_device *pdev = data;
> +	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
> +
> +	if (val && !dfll_is_running(pdev))
> +		clk_prepare_enable(td->dfll_clk);
> +	else if (!val && dfll_is_running(pdev))
> +		clk_disable_unprepare(td->dfll_clk);

This is going to allow unbalanced prepare_enable/disable_unprepare calls
simply by writing stuff to the debugfs file. Do we want to make debugfs
more friendly than that? If so, perhaps keep some state around to
indicate the last requested state from debugfs, and only make the clock
calls if the new requested state is different from the previous
debugfs-requested state, rather than different to the current-actual state?

> +static int dfll_register_show(struct seq_file *s, void *data)

regmap will give you this function for free. Perhaps its considered too
much overhead though?

> +static ssize_t dfll_register_write(struct file *file,
> +				   const char __user *userbuf, size_t count,
> +				   loff_t *ppos)
> +{
> +	struct platform_device *pdev = file->f_path.dentry->d_inode->i_private;
> +	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
> +	char buf[80];
> +	u32 offs, val;
> +
> +	if (sizeof(buf) <= count)
> +		return -EINVAL;

Don't you need to check whether ppos==0, to detect continuation rather
than initial writes?

For register twiddling, why not just use a user-space app that mmap()s
/dev/mem and blasts in the values? That would remove the need to put
this into the kernel. This also feels like a function that should be
implemented as a common utility, not open-coded into every driver. regmap?

> +static int dfll_debug_init(struct platform_device *pdev)
> +{
> +	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
> +	int ret;
> +
> +	if (!td || (td->mode == DFLL_UNINITIALIZED))
> +		return 0;

Isn't td->mode always == DFLL_UNINITIALIZED when this function is
called, since the mode is only set when dfll_enable() is called, which
is only called when dfll_clk_enable() is called, and the clock isn't
enabled when this function is called?

> +int tegra_dfll_register(struct platform_device *pdev,
> +			struct tegra_dfll_soc_data *soc)

> +	td = kzalloc(sizeof(*td), GFP_KERNEL);

devm_kzalloc would simplify error-handling.

> +	td->soc = kmemdup(soc, sizeof(struct tegra_dfll_soc_data), GFP_KERNEL);

Why not just store the pointer?

> +	td->base = ioremap(mem->start, resource_size(mem));

devm_ioremap()

> +	clk_put(td->i2c_clk);

I evidently should have mentioned devm_clk_get() somewhere too.

> +int tegra_dfll_unregister(struct platform_device *pdev)

> +	pm_runtime_put_sync(&pdev->dev);

What is that matching? dfll_init() does a matched get/put, as do
dfll_enable/disable, which in turn should be called in a matched fashion
from any client's clk_enable/disable.

> diff --git a/drivers/clk/tegra/clk-dfll.h b/drivers/clk/tegra/clk-dfll.h

> + * clk-dfll.h - prototypes and macros for the Tegra DFLL clocksource driver

Remove filename?
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 3089f05ba661..ecf004e71a75 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -117,6 +117,8 @@  config COMMON_CLK_KEYSTONE
           Supports clock drivers for Keystone based SOCs. These SOCs have local
 	  a power sleep control module that gate the clock to the IPs and PLLs.
 
+source "drivers/clk/tegra/Kconfig"
+
 endmenu
 
 source "drivers/clk/mvebu/Kconfig"
diff --git a/drivers/clk/tegra/Kconfig b/drivers/clk/tegra/Kconfig
new file mode 100644
index 000000000000..5c5d1a841ef8
--- /dev/null
+++ b/drivers/clk/tegra/Kconfig
@@ -0,0 +1,9 @@ 
+menu "Tegra clock support"
+	depends on COMMON_CLK
+
+config CLK_TEGRA_DFLL
+	tristate
+	depends on COMMON_CLK
+
+endmenu
+
diff --git a/drivers/clk/tegra/Makefile b/drivers/clk/tegra/Makefile
index f7dfb72884a4..03277f640bfe 100644
--- a/drivers/clk/tegra/Makefile
+++ b/drivers/clk/tegra/Makefile
@@ -15,3 +15,11 @@  obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += clk-tegra20.o
 obj-$(CONFIG_ARCH_TEGRA_3x_SOC)         += clk-tegra30.o
 obj-$(CONFIG_ARCH_TEGRA_114_SOC)	+= clk-tegra114.o
 obj-$(CONFIG_ARCH_TEGRA_124_SOC)	+= clk-tegra124.o
+obj-$(CONFIG_CLK_TEGRA_DFLL)		+= clk-dfll.o
+
+# XXX -Wno-sign-compare is only needed due to problems with
+# some non-clock-related Linux header files, and can be removed
+# once those headers are fixed
+CFLAGS_clk-dfll.o			+= -Wall -Wextra -Wno-unused-parameter \
+					   -Wno-sign-compare
+
diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c
new file mode 100644
index 000000000000..83a907fd4798
--- /dev/null
+++ b/drivers/clk/tegra/clk-dfll.c
@@ -0,0 +1,1229 @@ 
+/*
+ * clk-dfll.c - Tegra DFLL clock source common code
+ *
+ * Copyright (C) 2012-2013 NVIDIA Corporation.  All rights reserved.
+ *
+ * Aleksandr Frid <afrid@nvidia.com>
+ * Paul Walmsley <pwalmsley@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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.
+ *
+ * ...
+ *
+ * This library is for the DVCO and DFLL IP blocks on the Tegra114
+ * SoC.  These IP blocks together are also known at NVIDIA as
+ * "CL-DVFS".  To try to avoid confusion, this code refers to them
+ * collectively as the "DFLL."
+ *
+ * The DFLL is a root clocksource which tolerates some amount of
+ * supply voltage noise.  Tegra114 uses it to clock the fast CPU
+ * complex when the target CPU speed is above a particular rate.  The
+ * DFLL can be operated in either open-loop mode or closed-loop mode.
+ * In open-loop mode, the DFLL generates an output clock appropriate
+ * to the supply voltage.  In closed-loop mode, when configured with a
+ * target frequency, the DFLL minimizes supply voltage while
+ * delivering an average frequency equal to the target.  (This driver
+ * currently only supports open-loop mode.)
+ *
+ * Devices clocked by the DFLL must be able to tolerate frequency
+ * variation.  In the case of the CPU, it's important to note that the
+ * CPU cycle time will vary.  This has implications for
+ * performance-measurement code and any code that relies on the CPU
+ * cycle time to delay for a certain length of time.
+ *
+ * To do:
+ * - Missing kerneldoc on the debugfs functions
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/tegra-soc.h>
+
+#include "clk.h"
+#include "clk-dfll.h"
+
+/*
+ * DFLL register offset & bitfield macros
+ */
+
+/* DFLL_CTRL: DFLL control register */
+#define DFLL_CTRL			0x00
+#define DFLL_CTRL_MODE_MASK		0x03
+
+/* DFLL_CONFIG: DFLL sample rate control */
+#define DFLL_CONFIG			0x04
+#define DFLL_CONFIG_DIV_MASK		0xff
+#define DFLL_CONFIG_DIV_PRESCALE	32
+
+/* DFLL_TUNE0: delay line configuration register 0 */
+#define DFLL_TUNE0			0x0c
+
+/* DFLL_TUNE1: delay line configuration register 1 */
+#define DFLL_TUNE1			0x10
+
+/* DFLL_FREQ_REQ: target DFLL frequency control */
+#define DFLL_FREQ_REQ			0x14
+#define DFLL_FREQ_REQ_FORCE_ENABLE	(0x1 << 28)
+#define DFLL_FREQ_REQ_FORCE_SHIFT	16
+#define DFLL_FREQ_REQ_FORCE_MASK	(0xfff << DFLL_FREQ_REQ_FORCE_SHIFT)
+#define FORCE_MAX			2047
+#define FORCE_MIN			-2048
+#define DFLL_FREQ_REQ_SCALE_SHIFT	8
+#define DFLL_FREQ_REQ_SCALE_MASK	(0xff << DFLL_FREQ_REQ_SCALE_SHIFT)
+#define DFLL_FREQ_REQ_SCALE_MAX		256
+#define DFLL_FREQ_REQ_FREQ_VALID	(0x1 << 7)
+#define DFLL_FREQ_REQ_FREQ_SHIFT	0
+#define DFLL_FREQ_REQ_FREQ_MASK		(0x7f << DFLL_FREQ_REQ_FREQ_SHIFT)
+#define FREQ_MAX			127
+
+/* DFLL_DROOP_CTRL: droop prevention control */
+#define DFLL_DROOP_CTRL			0x1c
+
+/* DFLL_MONITOR_CTRL: loop data source control */
+#define DFLL_MONITOR_CTRL		0x28
+#define DFLL_MONITOR_CTRL_FREQ		6
+
+/* DFLL_MONITOR_DATA: internal monitoring */
+#define DFLL_MONITOR_DATA		0x2c
+#define DFLL_MONITOR_DATA_NEW_MASK	(0x1 << 16)
+#define DFLL_MONITOR_DATA_VAL_SHIFT	0
+#define DFLL_MONITOR_DATA_VAL_MASK	(0xFFFF << DFLL_MONITOR_DATA_VAL_SHIFT)
+
+/* DFLL_I2C_CFG: I2C controller configuration register */
+#define DFLL_I2C_CFG			0x40
+#define DFLL_I2C_CFG_ARB_ENABLE		(0x1 << 20)
+#define DFLL_I2C_CFG_HS_CODE_SHIFT	16
+#define DFLL_I2C_CFG_HS_CODE_MASK	(0x7 << DFLL_I2C_CFG_HS_CODE_SHIFT)
+#define DFLL_I2C_CFG_PACKET_ENABLE	(0x1 << 15)
+#define DFLL_I2C_CFG_SIZE_SHIFT		12
+#define DFLL_I2C_CFG_SIZE_MASK		(0x7 << DFLL_I2C_CFG_SIZE_SHIFT)
+#define DFLL_I2C_CFG_SLAVE_ADDR_10	(0x1 << 10)
+#define DFLL_I2C_CFG_SLAVE_ADDR_SHIFT	0
+#define DFLL_I2C_CFG_SLAVE_ADDR_MASK	(0x3ff << DFLL_I2C_CFG_SLAVE_ADDR_SHIFT)
+
+/* DFLL_I2C_STS: I2C controller status */
+#define DFLL_I2C_STS			0x48
+#define DFLL_I2C_STS_I2C_LAST_SHIFT	1
+#define DFLL_I2C_STS_I2C_REQ_PENDING	0x1
+
+/* DFLL_INTR_STS: DFLL interrupt status register */
+#define DFLL_INTR_STS			0x5c
+
+/* DFLL_INTR_EN: DFLL interrupt enable register */
+#define DFLL_INTR_EN			0x60
+#define DFLL_INTR_MIN_MASK		0x1
+#define DFLL_INTR_MAX_MASK		0x2
+
+/* DFLL_I2C_CLK_DIVISOR: I2C controller clock divisor */
+#define DFLL_I2C_CLK_DIVISOR		0x16c
+#define DFLL_I2C_CLK_DIVISOR_MASK	0xffff
+#define DFLL_I2C_CLK_DIVISOR_FS_SHIFT	16
+#define DFLL_I2C_CLK_DIVISOR_HS_SHIFT	0
+#define DFLL_I2C_CLK_DIVISOR_PREDIV	8
+#define DFLL_I2C_CLK_DIVISOR_HSMODE_PREDIV	12
+
+/*
+ * DFLL_OUTPUT_LUT: Offset to the start of the 33x8-bit voltage lookup
+ *     table. 32-bit aligned.  Used in I2C mode only.
+ */
+#define DFLL_OUTPUT_LUT			0x200
+
+/*
+ * Other constants
+ */
+
+/* MAX_DFLL_VOLTAGES: number of LUT entries in the DFLL IP block */
+#define MAX_DFLL_VOLTAGES		33
+
+/*
+ * REF_CLK_CYC_PER_DVCO_SAMPLE: the number of ref_clk cycles that the hardware
+ *    integrates the DVCO counter over - used for debug rate monitoring and
+ *    droop control
+ */
+#define REF_CLK_CYC_PER_DVCO_SAMPLE	4
+
+/*
+ * REF_CLOCK_RATE: the DFLL reference clock rate currently supported by this
+ * driver, in Hz
+ */
+#define REF_CLOCK_RATE			51000000
+
+/*
+ * DEFAULT_DROOP_CTRL_VAL: the default voltage droop prevention
+ * control register value.  Assumes reference clock rate is
+ * REF_CLOCK_RATE.
+ */
+#define DEFAULT_DROOP_CTRL_VAL		0x00000F00
+
+/**
+ * enum dfll_ctrl_mode - DFLL hardware operating mode
+ * @DFLL_UNINITIALIZED: (uninitialized state - not in hardware bitfield)
+ * @DFLL_DISABLED: DFLL not generating an output clock
+ * @DFLL_OPEN_LOOP: DVCO running, but DFLL not adjusting voltage
+ *
+ * The integer corresponding to the last two states, minus one, is
+ * written to the DFLL hardware to change operating modes.
+ */
+enum dfll_ctrl_mode {
+	DFLL_UNINITIALIZED = 0,
+	DFLL_DISABLED = 1,
+	DFLL_OPEN_LOOP = 2,
+};
+
+/**
+ * enum dfll_tune_range - voltage range that the driver believes it's in
+ * @DFLL_TUNE_UNINITIALIZED: DFLL tuning not yet programmed
+ * @DFLL_TUNE_LOW: DFLL in the low-voltage range (or open-loop mode)
+ *
+ * Some DFLL tuning parameters may need to change depending on the
+ * DVCO's voltage; these states represent the ranges that the driver
+ * supports.  These are software states; these values are never
+ * written into registers.
+ */
+enum dfll_tune_range {
+	DFLL_TUNE_UNINITIALIZED = 0,
+	DFLL_TUNE_LOW = 1,
+};
+
+/**
+ * struct dfll_clk_hw - DFLL clk_hw wrapper for the clock framework
+ * @pdev: DFLL platform_device *
+ * @hw: struct clk_hw - for use by the clock framework
+ *
+ * The @pdev is used by the DFLL driver's clock framework interface
+ * functions, to retrieve the DFLL context.
+ */
+struct dfll_clk_hw {
+	struct platform_device		*pdev;
+	struct clk_hw			hw;
+};
+
+/**
+ * struct tegra_dfll - context for a DFLL platform_device *
+ * @soc: pointer to SoC integration and characterization information
+ * @base: virtual address that the DFLL IP block MMIO space is mapped to
+ * @soc_clk: DFLL logic clock input - 51MHz
+ * @ref_clk: Reference clock input - 51MHz
+ * @i2c_clk: PWR_I2C controller clock input
+ * @dfll_clk: our output clock - registered in dfll_init()
+ * @ref_rate: clock rate of @ref_clk.  Does not change once set
+ * @tune_range: current voltage range that the driver believes it's in
+ * @mode: current DFLL hardware operating mode
+ * @freq_req_regval: cache of the DFLL_FREQ_REQ register value
+ * @speedo_id: CPU Speedo ID - from process characterization
+ * @process_id: CPU process ID - from process characterization
+ * @max_reg_offs: maximum register address offset (from the base) in the DFLL
+ * @dent: dentry for DFLL debugfs (at /DRIVER_NAME)
+ * @dfll_clk_hw: CCF clk_hw wrapper for the DFLL output clock
+ * @lock: spinlock to protect accesses to DFLL registers and state
+ *
+ * XXX @freq_req_regval must be re-read after the DFLL IP block loses
+ * context.
+ */
+struct tegra_dfll {
+	struct tegra_dfll_soc_data	*soc;
+
+	void __iomem			*base;
+
+	struct clk			*soc_clk;
+	struct clk			*ref_clk;
+	struct clk			*i2c_clk;
+	struct clk			*dfll_clk;
+	unsigned long			ref_rate;
+
+	enum dfll_ctrl_mode		mode;
+	enum dfll_tune_range		tune_range;
+
+	u32				freq_req_regval;
+
+	u8				speedo_id;
+	u8				process_id;
+
+	u16				max_reg_offs;
+
+	struct dentry			*dent;
+
+	struct dfll_clk_hw		dfll_clk_hw;
+	spinlock_t			lock;	/* see kerneldoc above */
+};
+
+#define to_dfll_clk_hw(_hw) container_of(_hw, struct dfll_clk_hw, hw)
+
+/* mode_name: map numeric DFLL modes to names for friendly console messages */
+static const char * const mode_name[] = {
+	[DFLL_UNINITIALIZED] = "uninitialized",
+	[DFLL_DISABLED] = "disabled",
+	[DFLL_OPEN_LOOP] = "open_loop",
+};
+
+/* Static functions */
+
+static inline u32 dfll_readl(struct tegra_dfll *td, u32 offs)
+{
+	return __raw_readl(td->base + offs);
+}
+
+static inline void dfll_writel(struct tegra_dfll *td, u32 val, u32 offs)
+{
+	__raw_writel(val, td->base + offs);
+}
+
+/**
+ * dfll_wmb - ensure all MMIO writes from the CPU to the DFLL have completed
+ * @td: struct tegra_dfll * device context
+ *
+ * Ensure that all writes from the CPU to the memory-mapped I/O space
+ * of the DFLL IP block have completed.  Assumes that the CPU that
+ * this code is currently running on has excluded other CPUs on the
+ * system from accessing the DFLL IP block MMIO space.
+ */
+static inline void dfll_wmb(struct tegra_dfll *td)
+{
+	dfll_readl(td, DFLL_CTRL);
+}
+
+/**
+ * dfll_is_running - is the DFLL currently generating a clock?
+ * @pdev: pointer to the DFLL platform_device
+ *
+ * If the DFLL is currently generating an output clock signal, return
+ * true; otherwise return false.
+ */
+static bool dfll_is_running(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	return (td->mode == DFLL_DISABLED ||
+		td->mode == DFLL_UNINITIALIZED) ? false : true;
+}
+
+/*
+ * Runtime PM suspend/resume callbacks
+ */
+
+/**
+ * tegra_dfll_runtime_resume - enable all clocks needed by the DFLL
+ * @dev: DFLL device *
+ *
+ * Enable all clocks needed by the DFLL.  Assumes that clk_prepare()
+ * has already been called on all the clocks.  No return value.
+ *
+ * XXX Should also handle context restore when returning from off.
+ */
+int tegra_dfll_runtime_resume(struct device *dev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_enable(td->i2c_clk);
+	if (ret) {
+		dev_err(dev, "could not enable I2C clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_enable(td->ref_clk);
+	if (ret) {
+		dev_err(dev, "could not enable ref clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_enable(td->soc_clk);
+	if (ret) {
+		dev_err(dev, "could not enable register clock: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_dfll_runtime_resume);
+
+/**
+ * tegra_dfll_runtime_suspend - disable all clocks needed by the DFLL
+ * @dev: DFLL device *
+ *
+ * Disable all clocks needed by the DFLL.  Assumes that other code
+ * will later call clk_unprepare().  Returns 0.
+ */
+int tegra_dfll_runtime_suspend(struct device *dev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(dev);
+
+	clk_disable(td->soc_clk);
+	clk_disable(td->ref_clk);
+	clk_disable(td->i2c_clk);
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_dfll_runtime_suspend);
+
+/*
+ * DFLL tuning operations (per-voltage-range tuning settings)
+ */
+
+/**
+ * dfll_set_tune_range - set the internal tune_range variable and log some debug
+ * @pdev: DFLL platform_device *
+ * @range: tune_range to change the internal state variable to
+ *
+ * Set the internal tune_range variable to @range, and optionally
+ * output some debug.  Does not affect the hardware.  No return value.
+ */
+static void dfll_set_tune_range(struct platform_device *pdev,
+				enum dfll_tune_range range)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	td->tune_range = range;
+	pr_debug("%s: set tune range %d\n", __func__, range);
+}
+
+/**
+ * dfll_tune_low - tune to DFLL and CPU settings valid for any voltage
+ * @pdev: DFLL platform_device *
+ *
+ * Tune the DFLL oscillator parameters and the CPU clock shaper for
+ * the low-voltage range.  These settings are valid for any voltage,
+ * but may not be optimal.  The top end of the low-voltage range is
+ * represented by the index (td->tune_high_out_start - 1).  No return
+ * value.
+ */
+static void dfll_tune_low(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	dfll_set_tune_range(pdev, DFLL_TUNE_LOW);
+
+	dfll_writel(td, td->soc->tune0_low, DFLL_TUNE0);
+	dfll_writel(td, td->soc->tune1, DFLL_TUNE1); /* XXX does not change */
+	dfll_wmb(td);
+
+	if (td->soc->set_clock_trimmers_low)
+		td->soc->set_clock_trimmers_low();
+}
+
+/*
+ * I2C output voltage forcing control
+ */
+
+/**
+ * dfll_disable_i2c_output_forcing - turn off I2C output forcing
+ * @pdev: DFLL platform_device *
+ *
+ * Prevent the DFLL's I2C voltage controller from forcing a specific
+ * target voltage value on the I2C bus, rather than using the target
+ * voltage from the control loop.  No return value.
+ */
+static void dfll_disable_i2c_output_forcing(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	if (!(td->freq_req_regval & DFLL_FREQ_REQ_FORCE_ENABLE))
+		return;
+
+	td->freq_req_regval &= ~DFLL_FREQ_REQ_FORCE_ENABLE;
+	dfll_writel(td, td->freq_req_regval, DFLL_FREQ_REQ);
+}
+
+/*
+ * Voltage droop control
+ */
+
+/**
+ * dfll_set_default_droop_ctrl - set the default droop control value
+ * @pdev: DFLL platform_device *
+ *
+ * Set the default voltage droop control value, a product of
+ * characterization.  Called during DFLL driver initialization, and
+ * upon recovery from a context loss event.  Returns 0 upon success or
+ * -ERANGE if the register bitfield value representing @min_rate
+ * cannot be programmed into the hardware.
+ */
+static void dfll_set_default_droop_ctrl(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	dfll_writel(td, DEFAULT_DROOP_CTRL_VAL, DFLL_DROOP_CTRL);
+	dfll_wmb(td);
+}
+
+/*
+ * Output clock scaler control
+ */
+
+/**
+ * dfll_disable_output_clock_scaler - directly output the clock from the DVCO
+ * @pdev: DFLL platform_device *
+ *
+ * Configure the output clock scaler (skipper 3) to not divide the
+ * DVCO output clock, if it's currently dividing the DVCO output
+ * clock.  No return value.
+ */
+static void dfll_disable_output_scaler(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	u32 v;
+
+	v = td->freq_req_regval >> DFLL_FREQ_REQ_SCALE_SHIFT;
+	v &= DFLL_FREQ_REQ_SCALE_MAX - 1;
+	if (v == DFLL_FREQ_REQ_SCALE_MAX - 1)
+		return;
+
+	td->freq_req_regval |= ((DFLL_FREQ_REQ_SCALE_MAX - 1) <<
+				DFLL_FREQ_REQ_SCALE_SHIFT);
+	dfll_writel(td, td->freq_req_regval, DFLL_FREQ_REQ);
+	dfll_wmb(td);
+}
+
+/**
+ * dfll_read_output_clock_scale - return the output clock scale
+ * @pdev: DFLL platform_device *
+ *
+ * Return the current output clock scaling dividend.  If this is equal
+ * to DFLL_FREQ_REQ_SCALE_MAX, then there's no scaling; otherwise, the
+ * DVCO's output rate will be scaled by the return value divided by
+ * DFLL_FREQ_REQ_SCALE_MAX.
+ */
+static u32 dfll_read_output_clock_scale(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	u32 s;
+
+	s = td->freq_req_regval;
+	s &= DFLL_FREQ_REQ_SCALE_MASK;
+	s >>= DFLL_FREQ_REQ_SCALE_SHIFT;
+	s += 1;
+
+	return s;
+}
+
+/*
+ * Monitor control
+ */
+
+/**
+ * dfll_calc_monitored_rate - convert DFLL_MONITOR_DATA_VAL rate into real freq
+ * @monitor_data: value read from the DFLL_MONITOR_DATA_VAL bitfield
+ * @ref_rate: DFLL reference clock rate
+ *
+ * Convert @monitor_data from DFLL_MONITOR_DATA_VAL units into cycles
+ * per second.  Returns the converted value.
+ */
+static u64  __attribute__((pure)) dfll_calc_monitored_rate(u32 monitor_data,
+							   unsigned long ref_rate)
+{
+	return monitor_data * (ref_rate / REF_CLK_CYC_PER_DVCO_SAMPLE);
+}
+
+/**
+ * dfll_read_monitor_rate - return the DFLL's output rate from internal monitor
+ * @pdev: DFLL platform_device *
+ *
+ * If the DFLL is enabled, return the last rate reported by the DFLL's
+ * internal monitoring hardware.  This works in both open-loop and
+ * closed-loop mode, and takes the output scaler setting into account.
+ * Assumes that the monitor was programmed to monitor frequency before
+ * the sample period started.  If the driver believes that the DFLL is
+ * currently uninitialized or disabled, it will return 0, since
+ * otherwise the DFLL monitor data register will return the last
+ * measured rate from when the DFLL was active.
+ */
+static u64 dfll_read_monitor_rate(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	unsigned long flags;
+	u32 v, s;
+	u64 pre_scaler_rate, post_scaler_rate;
+
+	if (td->mode == DFLL_DISABLED || td->mode == DFLL_UNINITIALIZED)
+		return 0;
+
+	spin_lock_irqsave(&td->lock, flags);
+
+	v = dfll_readl(td, DFLL_MONITOR_DATA);
+	v &= DFLL_MONITOR_DATA_VAL_MASK;
+	v >>= DFLL_MONITOR_DATA_VAL_SHIFT;
+	pre_scaler_rate = dfll_calc_monitored_rate(v, td->ref_rate);
+
+	s = dfll_read_output_clock_scale(pdev);
+	post_scaler_rate = (pre_scaler_rate * s) / DFLL_FREQ_REQ_SCALE_MAX;
+
+	spin_unlock_irqrestore(&td->lock, flags);
+
+	return post_scaler_rate;
+}
+
+
+/*
+ * DFLL mode switching
+ */
+
+/**
+ * dfll_set_open_loop_config - prepare to switch to open-loop mode
+ * @pdev: DFLL platform_device *
+ *
+ * Prepare to switch the DFLL to open-loop mode.  This switches the
+ * DFLL to the low-voltage tuning range, ensures that I2C output
+ * forcing is disabled, and disables the output clock rate scaler.
+ * The DFLL's low-voltage tuning range parameters must be
+ * characterized to keep the downstream device stable at any DVCO
+ * input voltage.  No return value.
+ */
+static void dfll_set_open_loop_config(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	/* always tune low (safe) in open loop */
+	if (td->tune_range != DFLL_TUNE_LOW)
+		dfll_tune_low(pdev);
+
+	dfll_disable_i2c_output_forcing(pdev);
+	dfll_disable_output_scaler(pdev);
+}
+
+/*
+ * DFLL enable/disable & open-loop <-> closed-loop transitions
+ */
+
+/**
+ * dfll_set_mode - change the DFLL control mode
+ * @pdev: DFLL platform_device *
+ * @mode: DFLL control mode (see enum dfll_ctrl_mode)
+ *
+ * Change the DFLL's operating mode between disabled, open-loop mode,
+ * and closed-loop mode, or vice versa.  No return value.
+ */
+static void dfll_set_mode(struct platform_device *pdev,
+			  enum dfll_ctrl_mode mode)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	td->mode = mode;
+	dfll_writel(td, mode - 1, DFLL_CTRL);
+	dfll_wmb(td);
+}
+
+/**
+ * dfll_disable - switch from open-loop mode to disabled mode
+ * @pdev: DFLL platform_device *
+ *
+ * Switch from OPEN_LOOP state to DISABLED state.  No return value.
+ */
+static void dfll_disable(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&td->lock, flags);
+
+	if (td->mode != DFLL_OPEN_LOOP) {
+		dev_err(&pdev->dev, "cannot disable DFLL in %s mode\n",
+			mode_name[td->mode]);
+		goto tdd_exit;
+	}
+
+	dfll_set_mode(pdev, DFLL_DISABLED);
+	pm_runtime_put_sync(&pdev->dev);
+
+tdd_exit:
+	spin_unlock_irqrestore(&td->lock, flags);
+}
+
+/**
+ * dfll_enable - switch a disabled DFLL to open-loop mode
+ * @pdev: DFLL platform_device *
+ *
+ * Switch from DISABLED state to OPEN_LOOP state.  Returns 0 upon success
+ * or -EPERM if the DFLL is not currently in open-loop mode.
+ */
+static int dfll_enable(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&td->lock, flags);
+
+	if (td->mode != DFLL_DISABLED) {
+		dev_err(&pdev->dev, "cannot enable DFLL in %s mode\n",
+			mode_name[td->mode]);
+		ret = -EPERM;
+		goto tde_exit;
+	}
+
+	pm_runtime_get_sync(&pdev->dev);
+	dfll_set_mode(pdev, DFLL_OPEN_LOOP);
+
+tde_exit:
+	spin_unlock_irqrestore(&td->lock, flags);
+	return ret;
+}
+
+/*
+ * DFLL initialization code
+ */
+
+/**
+ * dfll_init - Prepare the DFLL IP block for use
+ * @pdev: DFLL platform_device *
+ *
+ * Do everything necessary to prepare the DFLL IP block for use.  The
+ * DFLL will be left in DISABLED state.  Called by dfll_probe().
+ * Returns 0 upon success, or passes along the error from whatever
+ * function returned it.
+ */
+static int dfll_init(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	int ret;
+
+	if (td->soc->deassert_dvco_reset)
+		td->soc->deassert_dvco_reset();
+
+	ret = clk_prepare(td->i2c_clk);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to enable i2c_clk\n");
+		return ret;
+	}
+
+	/* Enable module clocks, release control logic reset */
+	ret = clk_prepare(td->ref_clk);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to enable ref_clk\n");
+		goto di_err1;
+	}
+
+	ret = clk_prepare(td->soc_clk);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to enable soc_clk\n");
+		goto di_err2;
+	}
+
+	pm_runtime_enable(&pdev->dev);
+	pm_runtime_get_sync(&pdev->dev);
+
+	td->ref_rate = clk_get_rate(td->ref_clk);
+	if (td->ref_rate == 0) {
+		dev_err(&pdev->dev, "ref_clk rate cannot be 0\n");
+		ret = -EINVAL;
+		goto di_err3;
+	}
+
+	if (td->ref_rate != REF_CLOCK_RATE) {
+		dev_err(&pdev->dev, "driver does not yet support reference clk rates other than %ul\n", REF_CLOCK_RATE);
+		ret = -EINVAL;
+		goto di_err3;
+	}
+
+	dfll_set_mode(pdev, DFLL_DISABLED);
+
+	td->freq_req_regval = dfll_readl(td, DFLL_FREQ_REQ);
+
+	dfll_writel(td, DFLL_MONITOR_CTRL_FREQ, DFLL_MONITOR_CTRL);
+	dfll_wmb(td);
+
+	dfll_set_default_droop_ctrl(pdev);
+
+	if (td->soc->init_clock_trimmers)
+		td->soc->init_clock_trimmers();
+
+	dfll_set_open_loop_config(pdev);
+
+	spin_lock_init(&td->lock);
+
+	pm_runtime_put_sync(&pdev->dev);
+
+	return 0;
+di_err3:
+	pm_runtime_put_sync(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	clk_unprepare(td->soc_clk);
+di_err2:
+	clk_unprepare(td->ref_clk);
+di_err1:
+	clk_unprepare(td->i2c_clk);
+
+	if (td->soc->assert_dvco_reset)
+		td->soc->assert_dvco_reset();
+
+	return ret;
+}
+
+/*
+ * Clock framework integration
+ */
+
+static int dfll_clk_is_enabled(struct clk_hw *hw)
+{
+	struct dfll_clk_hw *tdc = to_dfll_clk_hw(hw);
+
+	return dfll_is_running(tdc->pdev);
+}
+
+static int dfll_clk_enable(struct clk_hw *hw)
+{
+	struct dfll_clk_hw *tdc = to_dfll_clk_hw(hw);
+	struct platform_device *pdev = tdc->pdev;
+
+	return dfll_enable(pdev);
+}
+
+static void dfll_clk_disable(struct clk_hw *hw)
+{
+	struct dfll_clk_hw *tdc = to_dfll_clk_hw(hw);
+	struct platform_device *pdev = tdc->pdev;
+
+	dfll_disable(pdev);
+}
+
+static unsigned long dfll_clk_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct dfll_clk_hw *tdc = to_dfll_clk_hw(hw);
+	struct platform_device *pdev = tdc->pdev;
+	u64 rate;
+
+	/* XXX Return the target frequency instead in closed-loop mode? */
+	rate = dfll_read_monitor_rate(pdev);
+	WARN_ON(rate > ULONG_MAX);
+
+	return rate & ULONG_MAX;
+}
+
+static const struct clk_ops dfll_clk_ops = {
+	.is_enabled	= dfll_clk_is_enabled,
+	.enable		= dfll_clk_enable,
+	.disable	= dfll_clk_disable,
+	.recalc_rate	= dfll_clk_recalc_rate,
+};
+
+static const char *dfll_clk_parents[] = { "dfll_soc" };
+
+static struct clk_init_data dfll_clk_init_data = {
+	.ops		= &dfll_clk_ops,
+	.parent_names	= dfll_clk_parents,
+	.num_parents	= ARRAY_SIZE(dfll_clk_parents),
+};
+
+/**
+ * dfll_register_clk - register the DFLL output clock with the clock framework
+ * @pdev: DFLL platform_device *
+ *
+ * Register the DFLL's output clock (i.e., the clock source that the
+ * DVCO generates) with the Linux clock framework and clkdev.  Returns
+ * 0 upon success or -EINVAL upon failure.
+ */
+static int dfll_register_clk(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	int ret;
+
+	if (!td->soc->output_clock_name) {
+		dev_err(&pdev->dev, "missing output clock name\n");
+		return -EINVAL;
+	}
+
+	dfll_clk_init_data.name = td->soc->output_clock_name;
+	td->dfll_clk_hw.pdev = pdev;
+	td->dfll_clk_hw.hw.init = &dfll_clk_init_data;
+
+	td->dfll_clk = clk_register(&pdev->dev, &td->dfll_clk_hw.hw);
+	if (IS_ERR(td->dfll_clk)) {
+		dev_err(&pdev->dev, "DFLL clock registration error\n");
+		return -EINVAL;
+	}
+
+	ret = clk_register_clkdev(td->dfll_clk, td->soc->output_clock_name,
+				  NULL);
+	if (ret) {
+		dev_err(&pdev->dev, "DFLL clkdev registration error\n");
+		goto rdc_err;
+	}
+
+	return 0;
+rdc_err:
+	clk_unregister(td->dfll_clk);
+	return ret;
+}
+
+/**
+ * dfll_unregister_clk - unregister the DFLL output clock
+ * @pdev: DFLL platform_device *
+ *
+ * Unregister the DFLL's output clock from the Linux clock framework
+ * and from clkdev.  No return value.
+ */
+static void dfll_unregister_clk(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	/* XXX Why doesn't clk_unregister_clkdev() exist? */
+
+	clk_unregister(td->dfll_clk);
+	td->dfll_clk = NULL;
+}
+
+/*
+ * Debugfs interface
+ */
+
+#ifdef CONFIG_DEBUG_FS
+
+static int dfll_enable_get(void *data, u64 *val)
+{
+	struct platform_device *pdev = data;
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	*val = (td->mode == DFLL_OPEN_LOOP);
+
+	return 0;
+}
+static int dfll_enable_set(void *data, u64 val)
+{
+	struct platform_device *pdev = data;
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	if (val && !dfll_is_running(pdev))
+		clk_prepare_enable(td->dfll_clk);
+	else if (!val && dfll_is_running(pdev))
+		clk_disable_unprepare(td->dfll_clk);
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(enable_fops, dfll_enable_get, dfll_enable_set,
+			"%llu\n");
+
+static int dfll_output_rate_get(void *data, u64 *val)
+{
+	struct platform_device *pdev = data;
+
+	*val = dfll_read_monitor_rate(pdev);
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(output_rate_fops, dfll_output_rate_get, NULL, "%llu\n");
+
+static int dfll_register_show(struct seq_file *s, void *data)
+{
+	unsigned long flags;
+	u32 offs;
+	struct platform_device *pdev = s->private;
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	spin_lock_irqsave(&td->lock, flags);
+
+	seq_puts(s, "CONTROL REGISTERS:\n");
+	for (offs = 0; offs <= DFLL_MONITOR_DATA; offs += 4)
+		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
+			   dfll_readl(td, offs));
+
+	seq_puts(s, "\nI2C and INTR REGISTERS:\n");
+	for (offs = DFLL_I2C_CFG; offs <= DFLL_I2C_STS; offs += 4)
+		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
+			   dfll_readl(td, offs));
+
+	offs = DFLL_INTR_STS;
+	seq_printf(s, "[0x%02x] = 0x%08x\n", offs, dfll_readl(td, offs));
+	offs = DFLL_INTR_EN;
+	seq_printf(s, "[0x%02x] = 0x%08x\n", offs, dfll_readl(td, offs));
+	offs = DFLL_I2C_CLK_DIVISOR;
+	seq_printf(s, "[0x%02x] = 0x%08x\n", offs, dfll_readl(td, offs));
+
+	seq_puts(s, "\nLUT:\n");
+	for (offs = DFLL_OUTPUT_LUT;
+	     offs < DFLL_OUTPUT_LUT + 4 * MAX_DFLL_VOLTAGES;
+	     offs += 4)
+		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
+			   dfll_readl(td, offs));
+
+	spin_unlock_irqrestore(&td->lock, flags);
+
+	return 0;
+}
+
+static int dfll_register_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, dfll_register_show, inode->i_private);
+}
+
+static ssize_t dfll_register_write(struct file *file,
+				   const char __user *userbuf, size_t count,
+				   loff_t *ppos)
+{
+	struct platform_device *pdev = file->f_path.dentry->d_inode->i_private;
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	char buf[80];
+	u32 offs, val;
+
+	if (sizeof(buf) <= count)
+		return -EINVAL;
+
+	if (copy_from_user(buf, userbuf, count))
+		return -EFAULT;
+
+	buf[count] = '\0';
+
+	if (sscanf(buf, "[0x%8x] = 0x%8x", &offs, &val) != 2)
+		return -EINVAL;
+
+	if (offs > td->max_reg_offs)
+		return -EINVAL;
+
+	/* avoid unaligned accesses */
+	if ((offs & (~0x3)) != offs)
+		return -EINVAL;
+
+	dfll_writel(td, val, offs);
+	dfll_wmb(td);
+
+	return count;
+}
+
+static const struct file_operations dfll_register_fops = {
+	.open		= dfll_register_open,
+	.read		= seq_read,
+	.write		= dfll_register_write,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int dfll_debug_init(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	int ret;
+
+	if (!td || (td->mode == DFLL_UNINITIALIZED))
+		return 0;
+
+	td->dent = debugfs_create_dir(td->soc->driver_name, NULL);
+	if (!td->dent)
+		return -ENOMEM;
+
+	ret = -ENOMEM;
+
+	if (!debugfs_create_file("enable", S_IRUGO | S_IWUSR,
+				 td->dent, pdev, &enable_fops))
+		goto err_out;
+
+	if (!debugfs_create_file("output_rate", S_IRUGO,
+				 td->dent, pdev, &output_rate_fops))
+		goto err_out;
+
+	if (!debugfs_create_file("registers", S_IRUGO | S_IWUSR,
+				 td->dent, pdev, &dfll_register_fops))
+		goto err_out;
+
+	return 0;
+
+err_out:
+	debugfs_remove_recursive(td->dent);
+	return ret;
+}
+
+#endif		/* CONFIG_DEBUG_FS */
+
+/*
+ * DFLL initialization
+ */
+
+/**
+ * dfll_init_clks - clk_get() the DFLL source clocks
+ * @pdev: DFLL platform_device *
+ *
+ * Call clk_get() on the DFLL source clocks and save the pointers for later
+ * use.  Returns 0 upon success or -ENODEV if one or more of the clocks
+ * couldn't be looked up.
+ */
+static int dfll_init_clks(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+	int ret;
+
+	td->ref_clk = clk_get(&pdev->dev, "ref");
+	if (IS_ERR(td->ref_clk)) {
+		dev_err(&pdev->dev, "missing ref clock\n");
+		ret = -ENODEV;
+		goto tdic_err1;
+	}
+
+	td->soc_clk = clk_get(&pdev->dev, "soc");
+	if (IS_ERR(td->soc_clk)) {
+		dev_err(&pdev->dev, "missing soc clock\n");
+		ret = -ENODEV;
+		goto tdic_err2;
+	}
+
+	td->i2c_clk = clk_get(&pdev->dev, "i2c");
+	if (IS_ERR(td->i2c_clk)) {
+		dev_err(&pdev->dev, "missing i2c clock\n");
+		ret = -ENODEV;
+		goto tdic_err3;
+	}
+
+	return 0;
+
+tdic_err3:
+	clk_put(td->soc_clk);
+tdic_err2:
+	clk_put(td->ref_clk);
+tdic_err1:
+	return ret;
+}
+
+/*
+ * DT data fetch
+ */
+
+/*
+ * API exported to per-SoC platform drivers
+ */
+
+/**
+ * tegra_dfll_register - probe a Tegra DFLL device
+ * @pdev: DFLL platform_device *
+ * @soc: Per-SoC integration and characterization data for this DFLL instance
+ *
+ * Probe and initialize a DFLL device instance.  Intended to be called
+ * by a SoC-specific shim driver that passes in per-SoC integration
+ * and configuration data via @soc.  (The structure record pointed to
+ * by @soc is copied, so the caller can dispose of its own copy once
+ * tegra_dfll_register() returns.)  Returns 0 upon success, -EINVAL if
+ * @soc is null or if some resource data is missing, -ENOMEM if out of
+ * memory, or can pass along error codes from other functions.
+ */
+int tegra_dfll_register(struct platform_device *pdev,
+			struct tegra_dfll_soc_data *soc)
+{
+	struct resource *mem;
+	struct tegra_dfll *td;
+	int ret = -EINVAL;
+	int r;
+
+	if (!soc) {
+		dev_err(&pdev->dev, "missing SoC data\n");
+		return -EINVAL;
+	}
+
+	td = kzalloc(sizeof(*td), GFP_KERNEL);
+	if (!td)
+		return -ENOMEM;
+
+	td->soc = kmemdup(soc, sizeof(struct tegra_dfll_soc_data), GFP_KERNEL);
+	if (!td->soc) {
+		ret = -ENOMEM;
+		goto tdr_err1;
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "missing register MMIO resource\n");
+		ret = -EINVAL;
+		goto tdr_err2;
+	}
+
+	td->base = ioremap(mem->start, resource_size(mem));
+	if (!td->base) {
+		dev_err(&pdev->dev, "couldn't ioremap() register MMIO area\n");
+		ret = -EINVAL;
+		goto tdr_err2;
+	}
+
+	td->max_reg_offs = resource_size(mem) - sizeof(u32);
+
+	td->speedo_id = tegra_get_cpu_speedo_id();
+	td->process_id = tegra_get_cpu_process_id();
+
+	platform_set_drvdata(pdev, td);
+
+	r = dfll_init_clks(pdev);
+	if (r) {
+		dev_err(&pdev->dev, "DFLL clock init error\n");
+		ret = r;
+		goto tdr_err3;
+	}
+
+	/* Enable the clocks and set the device up */
+	ret = dfll_init(pdev);
+	if (ret)
+		goto tdr_err4;
+
+	ret = dfll_register_clk(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "DFLL clk registration failed\n");
+		goto tdr_err4;
+	}
+
+#ifdef CONFIG_DEBUG_FS
+	dfll_debug_init(pdev);
+#endif
+
+	dev_info(&pdev->dev, "initialized\n");
+
+	return 0;
+
+tdr_err4:
+	clk_put(td->i2c_clk);
+	clk_put(td->soc_clk);
+	clk_put(td->ref_clk);
+tdr_err3:
+	iounmap(td->base);
+tdr_err2:
+	kfree(td->soc);
+tdr_err1:
+	kfree(td);
+	return ret;
+}
+EXPORT_SYMBOL(tegra_dfll_register);
+
+/**
+ * tegra_dfll_unregister - release all of the DFLL driver resources for a device
+ * @pdev: DFLL platform_device *
+ *
+ * Unbind this driver from the DFLL hardware device represented by
+ * @pdev.  The DFLL must be disabled for this to succeed.  Returns 0
+ * upon success or -EBUSY if the DFLL is still active.
+ */
+int tegra_dfll_unregister(struct platform_device *pdev)
+{
+	struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
+
+	/* Try to prevent removal while the DFLL is active */
+	if (td->mode != DFLL_DISABLED) {
+		dev_err(&pdev->dev,
+			"must disable DFLL before removing driver\n");
+		return -EBUSY;
+	}
+
+	pm_runtime_put_sync(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	dfll_unregister_clk(pdev);
+	if (td->soc->assert_dvco_reset)
+		td->soc->assert_dvco_reset();
+	clk_put(td->i2c_clk);
+	clk_put(td->soc_clk);
+	clk_put(td->ref_clk);
+	iounmap(td->base);
+	kfree(td->soc);
+	kfree(td);
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_dfll_unregister);
diff --git a/drivers/clk/tegra/clk-dfll.h b/drivers/clk/tegra/clk-dfll.h
new file mode 100644
index 000000000000..2e002c8ef597
--- /dev/null
+++ b/drivers/clk/tegra/clk-dfll.h
@@ -0,0 +1,54 @@ 
+/*
+ * clk-dfll.h - prototypes and macros for the Tegra DFLL clocksource driver
+ * Copyright (C) 2013 NVIDIA Corporation.  All rights reserved.
+ *
+ * Aleksandr Frid <afrid@nvidia.com>
+ * Paul Walmsley <pwalmsley@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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 __DRIVERS_CLK_TEGRA_CLK_DFLL_H
+#define __DRIVERS_CLK_TEGRA_CLK_DFLL_H
+
+/**
+ * struct tegra_dfll_soc - SoC-specific hooks/integration for the DFLL driver
+ * @driver_name: name of the platform_driver representing the DFLL
+ * @output_clock_name: name of the DFLL's output clock
+ * @min_millivolts: minimum voltage (in mV) that the DFLL tuning is valid for
+ * @tune0_low: DFLL tuning register 0 (low voltage range)
+ * @tune0_high: DFLL tuning register 0 (high voltage range)
+ * @tune1: DFLL tuning register 1
+ * @assert_dvco_reset: fn ptr to place the DVCO in reset
+ * @deassert_dvco_reset: fn ptr to release the DVCO reset
+ * @set_clock_trimmers_high: fn ptr to tune clock trimmers for high voltage
+ * @set_clock_trimmers_low: fn ptr to tune clock trimmers for low voltage
+ */
+struct tegra_dfll_soc_data {
+	char *driver_name;
+	char *output_clock_name;
+	unsigned int min_millivolts;
+	u32 tune0_low;
+	u32 tune0_high;
+	u32 tune1;
+	void (*assert_dvco_reset)(void);
+	void (*deassert_dvco_reset)(void);
+	void (*init_clock_trimmers)(void);
+	void (*set_clock_trimmers_high)(void);
+	void (*set_clock_trimmers_low)(void);
+};
+
+int tegra_dfll_register(struct platform_device *pdev,
+			struct tegra_dfll_soc_data *soc);
+int tegra_dfll_unregister(struct platform_device *pdev);
+int tegra_dfll_runtime_suspend(struct device *dev);
+int tegra_dfll_runtime_resume(struct device *dev);
+
+#endif /* __DRIVERS_CLK_TEGRA_CLK_DFLL_H */