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@xxxxxxxxxx>. 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@xxxxxxxxxx> Cc: Aleksandr Frid <afrid@xxxxxxxxxx> Cc: Matthew Longnecker <mlongnecker@xxxxxxxxxx> Cc: Tezaswi Raja <traja@xxxxxxxxxx> Cc: Peter De Schrijver <pdeschrijver@xxxxxxxxxx> Cc: Prashant Gaikwad <pgaikwad@xxxxxxxxxx> --- 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 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@xxxxxxxxxx> + * Paul Walmsley <pwalmsley@xxxxxxxxxx> + * + * 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@xxxxxxxxxx> + * Paul Walmsley <pwalmsley@xxxxxxxxxx> + * + * 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 */ -- To unsubscribe from this list: send the line "unsubscribe linux-tegra" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html