4160 lines
117 KiB
C

/*
* clk-t124-dfll.c - clock provider support for the Tegra124 DFLL clock source
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* ...
*
* DFLL states:
*
* - DISABLED: control logic mode - DISABLED, output interface disabled,
* dfll in reset
* - OPEN_LOOP: control logic mode - OPEN_LOOP, output interface disabled,
* dfll is running "unlocked"
* - CLOSED_LOOP: control logic mode - CLOSED_LOOP, output interface enabled,
* dfll is running "locked"
*
* In the following code, 'ol' is used to abbreviate 'open loop', and
* 'cl' is used to abbreviate 'closed loop'.
*
* The IP block controlled by this driver is also known as "CL_DVFS".
*/
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/suspend.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/regulator/consumer.h>
#include <linux/thermal.h>
#include <linux/clk/tegra124-dfll.h>
/*
* XXX The fuse reading functions should be moved into a driver underneath
* drivers/, and their symbols exported via EXPORT_SYMBOL(), rather than
* using functions in arch/arm/mach-tegra/.
*/
#include <linux/tegra-soc.h>
#include "clk.h"
#define DRIVER_NAME "t124_dfll"
#define to_tegra_dfll_clk_hw(_hw) container_of(_hw, struct tegra_dfll_clk_hw, \
hw)
/*
* 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_PARAMS: Loop control register */
#define DFLL_PARAMS 0x08
#define DFLL_PARAMS_CG_SCALE (0x1 << 24)
#define DFLL_PARAMS_CG_SCALE_SHIFT 24
#define DFLL_PARAMS_FORCE_MODE_SHIFT 22
#define DFLL_PARAMS_FORCE_MODE_MASK (0x3 << DFLL_PARAMS_FORCE_MODE_SHIFT)
#define DFLL_PARAMS_CF_PARAM_SHIFT 16
#define DFLL_PARAMS_CF_PARAM_MASK (0x3f << DFLL_PARAMS_CF_PARAM_SHIFT)
#define DFLL_PARAMS_CI_PARAM_SHIFT 8
#define DFLL_PARAMS_CI_PARAM_MASK (0x7 << DFLL_PARAMS_CI_PARAM_SHIFT)
#define DFLL_PARAMS_CG_PARAM_SHIFT 0
#define DFLL_PARAMS_CG_PARAM_MASK (0xff << DFLL_PARAMS_CG_PARAM_SHIFT)
/* DFLL_TUNE0: delay line configuration register 0 */
#define DFLL_TUNE0 0x0c
#define DFLL_TUNE0_DLY_STK_SHIFT 16
#define DFLL_TUNE0_DLY_STK_MASK (0xff << DFLL_TUNE0_DLY_STK_SHIFT)
#define DFLL_TUNE0_DLY_SRAM_SHIFT 8
#define DFLL_TUNE0_DLY_SRAM_MASK (0xff << DFLL_TUNE0_DLY_SRAM_SHIFT)
#define DFLL_TUNE0_DLY_INV_SHIFT 0
#define DFLL_TUNE0_DLY_INV_MASK (0xff << DFLL_TUNE0_DLY_INV_SHIFT)
/* DFLL_TUNE1: delay line configuration register 1 */
#define DFLL_TUNE1 0x10
#define DFLL_TUNE1_DLY_FINE_SHIFT 11
#define DFLL_TUNE1_DLY_FINE_MASK (0x1ff << DFLL_TUNE1_DLY_FINE_SHIFT)
#define DFLL_TUNE1_DLY_WIRE_SHIFT 0
#define DFLL_TUNE1_DLY_WIRE_MASK (0x7ff << DFLL_TUNE1_DLY_WIRE_SHIFT)
/* 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 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_SCALE_RAMP: slope control for output frequency ramp */
#define DFLL_SCALE_RAMP 0x18
#define DFLL_SCALE_RAMP_RATE_MASK 0xf
/* DFLL_DROOP_CTRL: droop prevention control */
#define DFLL_DROOP_CTRL 0x1c
#define DFLL_DROOP_CTRL_MIN_FREQ_SHIFT 16
#define DFLL_DROOP_CTRL_MIN_FREQ_MASK (0xff << DFLL_DROOP_CTRL_MIN_FREQ_SHIFT)
#define DFLL_DROOP_CTRL_CUT_SHIFT 8
#define DFLL_DROOP_CTRL_CUT_MASK (0xf << DFLL_DROOP_CTRL_CUT_SHIFT)
#define DFLL_DROOP_CTRL_RAMP_SHIFT 0
#define DFLL_DROOP_CTRL_RAMP_MASK (0xff << DFLL_DROOP_CTRL_RAMP_SHIFT)
/* DFLL_OUTPUT_CFG: PMIC interface control */
#define DFLL_OUTPUT_CFG 0x20
#define DFLL_OUTPUT_CFG_I2C_ENABLE (0x1 << 30)
#define OUT_MASK 0x3f
#define DFLL_OUTPUT_CFG_SAFE_SHIFT 24
#define DFLL_OUTPUT_CFG_SAFE_MASK (OUT_MASK << DFLL_OUTPUT_CFG_SAFE_SHIFT)
#define DFLL_OUTPUT_CFG_MAX_SHIFT 16
#define DFLL_OUTPUT_CFG_MAX_MASK (OUT_MASK << DFLL_OUTPUT_CFG_MAX_SHIFT)
#define DFLL_OUTPUT_CFG_MIN_SHIFT 8
#define DFLL_OUTPUT_CFG_MIN_MASK (OUT_MASK << DFLL_OUTPUT_CFG_MIN_SHIFT)
/* DFLL_OUTPUT_FORCE: output forcing control */
#define DFLL_OUTPUT_FORCE 0x24
#define DFLL_OUTPUT_FORCE_ENABLE_SHIFT 6
#define DFLL_OUTPUT_FORCE_ENABLE_MASK (0x1 << DFLL_OUTPUT_FORCE_ENABLE_SHIFT)
#define DFLL_OUTPUT_FORCE_DEBOUNCE_CNT_SHIFT 0
#define DFLL_OUTPUT_FORCE_DEBOUNCE_CNT_MASK \
(0x3f << DFLL_OUTPUT_FORCE_DEBOUNCE_CNT_SHIFT)
/* DFLL_MONITOR_CTRL: loop data source control */
#define DFLL_MONITOR_CTRL 0x28
#define DFLL_MONITOR_CTRL_SRC_SHIFT 0
#define DFLL_MONITOR_CTRL_SRC_MASK (0x7 << DFLL_MONITOR_CTRL_SRC_SHIFT)
#define DFLL_MONITOR_CTRL_DISABLE 0
#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_MASK 0xFFFF
/* 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_VDD_REG_ADDR: PMIC internal register controlling VDD */
#define DFLL_I2C_VDD_REG_ADDR 0x44
#define DFLL_I2C_DEFAULT_DATA_SHIFT 8
#define DFLL_I2C_DEFAULT_DATA_MASK (0xff << DFLL_I2C_DEFAULT_DATA_SHIFT)
#define DFLL_I2C_ADDR_DATA_SHIFT 0
#define DFLL_I2C_ADDR_DATA_MASK (0xff << DFLL_I2C_ADDR_DATA_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
*/
/*
* DFLL_CALIBR_TIME: minimum time interval between DFLL calibration
* attempts, in microseconds
*/
#define DFLL_CALIBR_TIME 40000
/*
* DFLL_OUTPUT_PENDING_TIMEOUT: if the code waits for longer than
* DFLL_OUTPUT_PENDING_TIMEOUT microseconds for in-progress I2C
* commands to the PMIC to complete, then indicate a timeout error.
*/
#define DFLL_OUTPUT_PENDING_TIMEOUT 1000
/*
* DFLL_OUTPUT_RAMP_DELAY: after the DFLL's I2C PMIC output starts
* requesting the minimum TUNE_HIGH voltage, the minimum number of
* microseconds to wait for the PMIC's voltage output to finish
* slewing
*/
#define DFLL_OUTPUT_RAMP_DELAY 100
/*
* DFLL_TUNE_HIGH_DELAY: number of microseconds to wait between tests
* to see if the high voltage has been reached yet, during a
* transition from the low-voltage range to the high-voltage range
*/
#define DFLL_TUNE_HIGH_DELAY 2000
/*
* DFLL_TUNE_HIGH_MARGIN_STEPS: attempt to initially program the DFLL
* voltage target to a (DFLL_TUNE_HIGH_MARGIN_STEPS * 10 millivolt)
* margin above the high voltage floor, in closed-loop mode in the
* high-voltage range
*/
#define DFLL_TUNE_HIGH_MARGIN_STEPS 2
/* MAX_DFLL_VOLTAGES: number of LUT entries */
#define MAX_DFLL_VOLTAGES 33
/*
* MAX_THERMAL_CAPS: maximum number of thermal cap (temperature,
* voltage) tuples that can be specified in the driver data.
*/
#define MAX_THERMAL_CAPS 8
/*
* MAX_THERMAL_FLOORS: maximum number of thermal floor (temperature,
* voltage) tuples that can be specified in the driver data.
*/
#define MAX_THERMAL_FLOORS 8
/*
* MAX_DVFS_FREQS: maximum number of DFLL output frequencies that this
* driver supports.
*/
#define MAX_DVFS_FREQS 40
/*
* CVB_ROW_WIDTH: in the DT data, this represents the number of cells
* in each CVB table entry
*/
#define CVB_ROW_WIDTH 4
/*
* THERM_CAPS_ROW_WIDTH: in the DT data, this represents the number
* of cells in each thermal cap table entry
*/
#define THERM_CAPS_ROW_WIDTH 2
/*
* THERM_FLOORS_ROW_WIDTH: in the DT data, this represents the number
* of cells in each thermal floor table entry
*/
#define THERM_FLOORS_ROW_WIDTH 2
/*
* DFLL_OUTPUT_CLOCK_NAME: the output clock name representing the DVCO
* output rate -- registered with the clock framework.
*/
#define DFLL_OUTPUT_CLOCK_NAME "dfllCPU_out"
/*
* I2C_OUTPUT_ACTIVE_TEST_US: mandatory minimum interval (in
* microseconds) between testing whether the I2C controller is
* currently sending a voltage-set command. Some comments list this
* as being a worst-case margin for "disable propagation."
*/
#define I2C_OUTPUT_ACTIVE_TEST_US 2
/**
* enum tegra_dfll_force_mode - output voltage forcing mode during freq change
* %TEGRA_DFLL_FORCE_NONE: no I2C output forcing
* %TEGRA_DFLL_FORCE_FIXED: force for a fixed number of sample periods
* %TEGRA_DFLL_FORCE_AUTO: force for a calculated number of sample periods
*
* Controls whether the output voltage index that the DFLL
* communicates to the PMIC is forced to a certain value during
* frequency changes, and if so, how long the forced value persists.
* Only applies to I2C control.
*/
enum tegra_dfll_force_mode {
TEGRA_DFLL_FORCE_NONE = 0,
TEGRA_DFLL_FORCE_FIXED = 1,
TEGRA_DFLL_FORCE_AUTO = 2,
};
/**
* struct tegra_dfll_voltage_reg_map - map of voltages to PMIC register values
* @reg_value: PMIC voltage control register value that produces @reg_uV
* @reg_uv: microvolts DC that the PMIC will generate when @reg_value is used
*
* Maps a voltage @reg_uv to the PMIC voltage-set register value @reg_value.
*/
struct tegra_dfll_voltage_reg_map {
u8 reg_value;
int reg_uv;
};
/**
* enum tegra_dfll_ctrl_mode - DFLL hardware operating mode
* @TEGRA_DFLL_UNINITIALIZED: (uninitialized state - not in hardware bitfield)
* @TEGRA_DFLL_DISABLED: DFLL not generating an output clock
* @TEGRA_DFLL_OPEN_LOOP: DVCO running, but DFLL not adjusting voltage
* @TEGRA_DFLL_CLOSED_LOOP: DVCO running and DFLL adjusting PMIC voltage
*
* The integer corresponding to the last three states, minus one, is
* written to the DFLL hardware to change operating modes.
*/
enum tegra_dfll_ctrl_mode {
TEGRA_DFLL_UNINITIALIZED = 0,
TEGRA_DFLL_DISABLED = 1,
TEGRA_DFLL_OPEN_LOOP = 2,
TEGRA_DFLL_CLOSED_LOOP = 3,
};
/**
* enum tegra_dfll_tune_state - state of the voltage-regime switching code
* @TEGRA_DFLL_TUNE_LOW: DFLL in the low-voltage range (or open-loop mode)
* @TEGRA_DFLL_TUNE_WAIT_DFLL: waiting for DFLL voltage output to reach high
* @TEGRA_DFLL_TUNE_WAIT_PMIC: waiting for PMIC to react to DFLL output
* @TEGRA_DFLL_TUNE_HIGH: DFLL in the high-voltage range
*
* These are software states; these values are never written into
* registers.
*/
enum tegra_dfll_tune_state {
TEGRA_DFLL_TUNE_LOW = 0,
TEGRA_DFLL_TUNE_WAIT_DFLL,
TEGRA_DFLL_TUNE_WAIT_PMIC,
TEGRA_DFLL_TUNE_HIGH,
};
/**
* struct dfll_rate_req - target DFLL rate request data
* @freq: value to program to the FREQ bitfield of the DFLL_FREQ_REQ register
* @scale: value to program to the SCALE bitfield of DFLL_FREQ_REQ
* @output: desired voltage to force the PMIC voltage output to (LUT index)
* @cap: unconditionally safe voltage for this frequency
* @rate: frequency in Hz corresponding to @freq
*
* When in closed-loop mode, there is guaranteed to be some voltage
* margin below @cap for the DFLL to adjust down to.
*/
struct dfll_rate_req {
u8 freq;
u8 scale;
u8 output;
u8 cap;
unsigned long rate;
};
/*
* Possible values for struct tegra_dfll.flags:
*
* TEGRA_DFLL_FLAGS_I2C_FORCE_QUIET: disable the DFLL's PMIC voltage
* control output before disabling the DFLL IP block. Set when the
* 'i2c-quiet-output-workaround' is present in the DT data for the
* DFLL IP block.
*/
#define TEGRA_DFLL_FLAGS_I2C_FORCE_QUIET BIT(0)
/**
* struct tegra_dfll_cvb - CVB table
* @freq: (DT) target DVCO frequency
* @c0: (DT) DFLL calibration constant 0
* @c1: (DT) DFLL calibration constant 1
* @c2: (DT) DFLL calibration constant 2
*
* Voltage curve data, indexed by @freq. This data comes from DT.
*/
struct tegra_dfll_cvb {
unsigned long freq;
int c0;
int c1;
int c2;
};
/**
* struct tegra_dfll_therm - thermally-sensitive voltage caps
* @temp: maximum trip temperature
* @output: PMIC voltage output at @temp or below
* @mv: minimum voltage output, in millivolts, at @temp or below
*/
struct tegra_dfll_therm {
u8 temp;
u8 output;
u16 mv;
};
/**
* struct tegra_dfll_clk_hw - DFLL clk_hw wrapper for the clock framework
* @pdev: DFLL instance
* @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 tegra_dfll_clk_hw {
struct platform_device *pdev;
struct clk_hw hw;
};
/**
* struct tegra_dfll_dvfs_info - all frequency and voltage related table
*
* @num_freqs: number of total entries in @freqs
* @num_voltages: number of entries in out_map
* @cvb_table_len: number of CVB table entries (see @cvb_table)
* @freqs: map from CVB table index to target output clock frequency
* @cpu_dfll_millivolts: map from CVB table index to output voltage (in mV)
* @clk_dvfs_map: map from CVB table index to output LUT index
* @cvb_table: CVB table data, from DT
*
* @clk_dvfs_map: output voltage mapping: legacy dvfs table index -to-
* cl_dvfs output LUT index; cl_dvfs output LUT index -to- PMU
* value/voltage pair ptr
*/
struct tegra_dfll_dvfs_info {
u8 num_freqs;
u8 num_voltages;
u8 cvb_table_len;
unsigned long freqs[MAX_DVFS_FREQS];
int cpu_dfll_millivolts[MAX_DVFS_FREQS];
u8 clk_dvfs_map[MAX_DVFS_FREQS];
struct tegra_dfll_cvb *cvb_table;
};
/**
* struct tegra_dfll - context for a DFLL instance
* @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: I2C5 controller clock input
* @dfll_clk: our output clock - registered in dfll_init()
* @ref_rate: clock rate of @ref_clk. Does not change once set
* @vdd_map_size: number of entries in @vdd_map
* @vdd_map: list of all possible voltages & corresponding PMIC VSELs
* @out_map: map from LUT index to voltage & PMIC VSEL - before clamping
* @safe_output: LUT index of the minimum safe voltage (OPEN_LOOP or DISABLED)
* @tune_high_out_start: LUT index to start with when tuning to high range
* @tune_high_out_min: LUT index of the minimum high tuning range voltage
* @minimax_output: LUT index of the minimum level of the maximum CL voltage
* @dvco_rate_min: @out_rate_min, rounded to (@ref_rate / 2)
* @lut_min: LUT index of the minimum voltage to send to the PMIC
* @lut_max: LUT index of the maximum voltage to send to the PMIC
* @therm_caps_idx: LUT index of the maximum volatage
* @therm_floors_idx: LUT index of the minimum voltage (due to cold die effects)
* @last_req: most recent closed-loop output frequency request
* @tune_state: in closed-loop mode: is the DFLL in low- or high-voltage regime?
* @mode: DFLL hardware operating mode: disabled, open-loop, or closed-loop
* @resume_mode: DFLL mode to be set to when resuming
* @tune_timer: timer used to wait during TUNE_HIGH_REQUEST for high Vdd
* @tune_delay: interval between tune_timer_cb calls during TUNE_HIGH_REQUEST
* @flags: (see "Possible values for struct tegra_dfll.flags" above)
* @tune0_low_voltage_range: DFLL_TUNE0 reg value in the low-voltage regime
* @tune0_high_voltage_range: DFLL_TUNE0 reg value in the high-voltage regime
* @tune1: DFLL_TUNE1 reg value (valid for both voltage regimes)
* @droop_rate_min: min ring osc freq before voltage droop control is enabled
* @tune_high_min_mv: starting voltage (in mV) of the high-voltage tuning range
* @dent: dentry for DFLL debugfs (at /DRIVER_NAME)
* @sample_rate: control loop sample rate
* @force_mode: I2C: force PMIC voltage during a freq change? if so, how?
* @cf: I2C: duration to force the PMIC voltage after frequency change
* @cg: loop gain (signed)
* @cg_scale: set to 1 to divide loop gain by 8
* @ci: loop integral gain selector
* @droop_cut_value: control output clock scaler at minimum ring osc freq
* @droop_restore_ramp: clock recovery rate after a voltage droop event
* @out_rate_min: "FmaxAtVmin": DFLL max operating freq @ minimum voltage
* @scale_out_ramp: voltage output ramp rate
* @min_millivolts: minimum voltage (in millivolts)
* @cvb_max_millivolts: CVB maximum voltage limit (in millivolts)
* @cvb_speedo_scale: silicon characterization constant - from fuses
* @cvb_voltage_scale: CVB voltage scale factor (to microvolts)
* @speedo_id: CPU Speedo ID - from process characterization
* @process_id: CPU process ID - from process characterization
* @speedo_value: CPU Speedo value - from process characterization
* @pmic_i2c_addr: PMIC I2C address (when I2C interface is active)
* @pmic_i2c_voltage_reg: PMIC I2C voltage control register
* @pmic_i2c_fs_rate: I2C FS bus rate
* @pmic_i2c_hs_rate: I2C HS bus rate - set to talk to PMIC in HS mode
* @pmic_i2c_hs_master_code: PMIC I2C HS master code (only needed for HS mode)
* @pmic_i2c_ten_bit_addrs: use 10-bit I2C address to address PMIC
* @vdd: regulator controlling the DFLL's voltage rail
* @vdd_step: linear step size between VSEL values (from regulator framework)
* @dfll_min_microvolt: lowest voltage the DFLL should try to program (from DT)
* @dfll_max_microvolt: highest voltage the DFLL should try to program (from DT)
* @therm_caps_num: number of entries in @therm_caps
* @therm_caps: maximum voltage caps at various temperatures
* @therm_floors_num: number of entries in @therm_floors
* @therm_floors: minimum voltage floors at various cold die temperatures
* @calibration_timer: timer to periodically recalibrate dvco_min_rate
* @calibration_delay: time interval between calibrations - see DFLL_CALIBR_TIME
* @last_calibration: ktime_t that the last calibration started
* @calibration_range_min: absolute minimum rate of the DFLL calibration range
* @calibration_range_max: absolute maximum rate of the DFLL calibration range
* @cdev: pointer to registered DFLL thermal device (if loaded)
* @lock: spinlock to protect accesses to DFLL registers and state
*
* @dfll_min_microvolt must be greater than or equal to the PMIC
* regulator's low voltage limit. @dfll_max_microvolt must be lesser
* than or equal to the PMIC's high voltage limit.
*/
struct tegra_dfll {
void __iomem *base;
struct clk *soc_clk;
struct clk *ref_clk;
struct clk *i2c_clk;
struct clk *dfll_clk;
unsigned long ref_rate;
u8 safe_output;
u8 tune_high_out_start;
u8 tune_high_out_min;
u8 minimax_output;
u8 lut_min;
u8 lut_max;
u8 therm_caps_idx;
u8 therm_caps_num;
u8 therm_floors_idx;
u8 therm_floors_num;
u8 flags;
u8 vdd_map_size;
u8 cg_scale;
struct tegra_dfll_voltage_reg_map *vdd_map;
struct tegra_dfll_voltage_reg_map *out_map[MAX_DFLL_VOLTAGES];
struct tegra_dfll_dvfs_info *dvfs_info;
unsigned long dvco_rate_min;
struct tegra_dfll_therm therm_caps[MAX_THERMAL_CAPS];
struct tegra_dfll_therm therm_floors[MAX_THERMAL_FLOORS];
struct dfll_rate_req last_req;
enum tegra_dfll_tune_state tune_state;
enum tegra_dfll_ctrl_mode mode;
enum tegra_dfll_ctrl_mode resume_mode;
struct timer_list tune_timer;
unsigned long tune_delay;
u32 tune0_low_voltage_range;
u32 tune0_high_voltage_range;
u32 tune1;
u32 droop_rate_min;
u32 tune_high_min_mv;
struct dentry *dent;
u32 sample_rate;
u32 force_mode;
u32 cf;
u32 ci;
u32 cg;
u32 droop_cut_value;
u32 droop_restore_ramp;
unsigned long out_rate_min;
u32 scale_out_ramp;
u32 min_millivolts;
u32 cvb_max_millivolts;
int cvb_speedo_scale; /* must be signed */
int cvb_voltage_scale; /* must be signed */
int speedo_id;
int process_id;
int speedo_value;
u32 pmic_i2c_addr;
u32 pmic_i2c_voltage_reg;
u32 pmic_i2c_fs_rate;
u32 pmic_i2c_hs_rate;
u32 pmic_i2c_hs_master_code;
u32 pmic_i2c_ten_bit_addrs;
struct regulator *vdd;
int vdd_step; /* must be signed */
u32 dfll_min_microvolt;
u32 dfll_max_microvolt;
struct timer_list calibration_timer;
unsigned long calibration_delay;
ktime_t last_calibration;
unsigned long calibration_range_min;
unsigned long calibration_range_max;
struct tegra_dfll_clk_hw dfll_clk_hw;
struct thermal_cooling_device *cdev_floor;
struct thermal_cooling_device *cdev_cap;
spinlock_t lock; /* see kerneldoc above */
};
/*
* Conversion macros (different scales for frequency request, and
* monitored rate is not a typo)
*/
#define RATE_STEP(td) ((td)->ref_rate / 2)
#define GET_REQUEST_FREQ(rate, ref_rate) ((rate) / ((ref_rate) / 2))
#define GET_REQUEST_RATE(freq, ref_rate) ((freq) * ((ref_rate) / 2))
#define GET_MONITORED_RATE(freq, ref_rate) ((freq) * ((ref_rate) / 4))
#define GET_DROOP_FREQ(rate, ref_rate) ((rate) / ((ref_rate) / 4))
#define ROUND_MIN_RATE(rate, ref_rate) \
(DIV_ROUND_UP(rate, (ref_rate) / 2) * ((ref_rate) / 2))
#define GET_DIV(ref_rate, out_rate, scale) \
DIV_ROUND_UP((ref_rate), (out_rate) * (scale))
/* fcpu_dfll_pdev: the DFLL platform device instance */
static struct platform_device *fcpu_dfll_pdev;
/* mode_name: map numeric DFLL modes to names for friendly console messages */
static const char * const mode_name[] = {
[TEGRA_DFLL_UNINITIALIZED] = "uninitialized",
[TEGRA_DFLL_DISABLED] = "disabled",
[TEGRA_DFLL_OPEN_LOOP] = "open_loop",
[TEGRA_DFLL_CLOSED_LOOP] = "closed_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);
}
/**
* i2c_output_enable - enable generation of voltage adjustment I2C commands
* @pdev: DFLL instance
* @val: ptr to the value to write to DFLL_OUTPUT_CFG register
*
* Tell the DFLL-I2C interface to start transmitting voltage-set
* commands to the PMIC. The variable pointed to by @v must be loaded
* from DFLL_OUTPUT_CFG, or manually set, before calling this code.
* No return value.
*/
static void i2c_output_enable(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
val = dfll_readl(td, DFLL_OUTPUT_CFG);
val |= DFLL_OUTPUT_CFG_I2C_ENABLE;
dfll_writel(td, val, DFLL_OUTPUT_CFG);
dfll_wmb(td);
}
/**
* i2c_output_disable - stop generating voltage adjustment I2C commands
* @pdev: DFLL instance
* @val: ptr to the value of the DFLL_OUTPUT_CFG register
*
* Tell the DFLL-I2C interface to stop transmitting voltage-set
* commands to the PMIC. The variable pointed to by @v must be loaded
* from DFLL_OUTPUT_CFG, or manually set, before calling this code.
* Most code should not call this directly - instead call
* i2c_output_disable_flush(). No return value.
*/
static void i2c_output_disable(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
val = dfll_readl(td, DFLL_OUTPUT_CFG);
val &= ~DFLL_OUTPUT_CFG_I2C_ENABLE;
dfll_writel(td, val, DFLL_OUTPUT_CFG);
dfll_wmb(td);
}
/**
* is_output_i2c_req_pending - is an I2C voltage-set command in progress?
* @pdev: DFLL instance
*
* Returns 1 if an I2C request is in progress, or 0 if not. The DFLL
* IP block requires two back-to-back reads of the I2C_REQ_PENDING
* field to return 0 before the software can be sure that no I2C
* request is currently pending. Also, a minimum time interval
* between DFLL_I2C_STS reads is required by the IP block.
*/
static int is_output_i2c_req_pending(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 sts;
sts = dfll_readl(td, DFLL_I2C_STS);
if (sts & DFLL_I2C_STS_I2C_REQ_PENDING)
return 1;
udelay(I2C_OUTPUT_ACTIVE_TEST_US);
sts = dfll_readl(td, DFLL_I2C_STS);
if (sts & DFLL_I2C_STS_I2C_REQ_PENDING)
return 1;
return 0;
}
/**
* i2c_output_disable_flush - disable I2C output and wait for I2C command to finish
* @pdev: DFLL instance
*
* Prevent the DFLL I2C controller from sending any further
* voltage-set commands, then wait for any in-progress commands to
* complete. This is the normal (non-workaround) path. Returns 0 if
* the flush completed within the timeout interval, or -ETIMEDOUT
* otherwise.
*/
static int i2c_output_disable_flush(struct platform_device *pdev)
{
int i, t;
t = DFLL_OUTPUT_PENDING_TIMEOUT / I2C_OUTPUT_ACTIVE_TEST_US;
i2c_output_disable(pdev);
for (i = 0; i < t; i++) {
if (!is_output_i2c_req_pending(pdev))
return 0;
udelay(I2C_OUTPUT_ACTIVE_TEST_US);
}
/* I2C request is still pending - report error */
return -ETIMEDOUT;
}
/**
* set_mode - change the DFLL control mode
* @pdev: DFLL instance
* @mode: DFLL control mode (see enum tegra_dfll_ctrl_mode)
*
* Change the DFLL's operating mode from open-loop mode to closed-loop
* mode, or vice versa. No return value.
*/
static void set_mode(struct platform_device *pdev,
enum tegra_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);
}
/**
* get_output_cap - return LUT index of the maximum non-DFLL safe voltage
* @pdev: DFLL instance
*
* Return the LUT index of the maximum non-DFLL safe voltage, given
* the current temperature of the SoC.
*/
static u8 get_output_cap(struct platform_device *pdev,
struct dfll_rate_req *req)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 thermal_cap = td->dvfs_info->num_voltages - 1;
if (td->therm_caps_idx && (td->therm_caps_idx <= td->therm_caps_num))
thermal_cap = td->therm_caps[td->therm_caps_idx - 1].output;
if (req && (req->cap < thermal_cap))
return req->cap;
return thermal_cap;
}
/**
* get_output_min - return LUT index of the minimum non-DFLL safe voltage
* @pdev: DFLL instance
*
* Return the LUT index of the minimum non-DFLL safe voltage, given
* the DFLL's current tuning state and the current temperature of the
* SoC.
*/
static u8 get_output_min(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 tune_min, thermal_min;
tune_min = (td->tune_state == TEGRA_DFLL_TUNE_LOW) ?
0 : td->tune_high_out_min;
thermal_min = 0;
if (td->therm_floors_idx < td->therm_floors_num)
thermal_min = td->therm_floors[td->therm_floors_idx].output;
return max(tune_min, thermal_min);
}
/*
* Voltage lookup table operations
*/
/**
* _load_lut - load voltage lookup table into DFLL RAM
* @pdev: DFLL instance
*
* Load the voltage-to-PMIC register value lookup table into the DFLL
* IP block LUT memory. td->lut_min and td->lut_max are used to cap
* the minimum and maximum voltage requested. This function shouldn't
* be called directly by code other than dfll_load_lut(), since this
* function doesn't handle the necessary pre- and post-requisites. No
* return value.
*/
static void _load_lut(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i;
u32 val;
val = td->out_map[td->lut_min]->reg_value;
for (i = 0; i <= td->lut_min; i++)
dfll_writel(td, val, DFLL_OUTPUT_LUT + i * 4);
for (; i < td->lut_max; i++) {
val = td->out_map[i]->reg_value;
dfll_writel(td, val, DFLL_OUTPUT_LUT + i * 4);
}
val = td->out_map[td->lut_max]->reg_value;
for (; i < td->dvfs_info->num_voltages; i++)
dfll_writel(td, val, DFLL_OUTPUT_LUT + i * 4);
dfll_wmb(td);
}
/**
* dfll_load_lut - load the voltage lookup table, disabling output if needed
* @td: struct tegra_dfll *
*
* Load the voltage-to-PMIC register value lookup table into the DFLL
* IP block memory, disabling and re-enabling the I2C output if
* needed. Look-up tables can be loaded at any time. No return
* value.
*/
static void dfll_load_lut(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val = dfll_readl(td, DFLL_OUTPUT_CFG);
int output_quiet_before_disable;
bool disable_out_for_load;
output_quiet_before_disable = (td->flags &
TEGRA_DFLL_FLAGS_I2C_FORCE_QUIET);
/*
* If the I2C output can be disabled at any time, and it's currently
* enabled, then disable it.
*/
disable_out_for_load = !output_quiet_before_disable &&
(val & DFLL_OUTPUT_CFG_I2C_ENABLE);
if (disable_out_for_load) {
i2c_output_disable(pdev);
udelay(I2C_OUTPUT_ACTIVE_TEST_US);
}
_load_lut(pdev);
/* Re-enable the I2C output if we disabled it previously. */
if (disable_out_for_load)
i2c_output_enable(pdev);
}
/**
* set_tune_state - set the internal tune state variable and log some debug
* @pdev: DFLL instance
* @state: tune_state to change the internal state variable to
*
* Set the internal tune_state variable to @state, and optionally output some
* debug. Does not affect the hardware. No return value.
*/
static void set_tune_state(struct platform_device *pdev,
enum tegra_dfll_tune_state state)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
td->tune_state = state;
pr_debug("%s: set tune state %d\n", __func__, state);
}
/**
* tune_low - fine-tune DFLL and CPU clock shaper for low voltages
* @pdev: DFLL instance
* @state: tune_state to change the internal state variable to
*
* Fine-tune the DFLL oscillator parameters and the CPU clock shaper for
* the low-voltage range. The top end of the low-voltage range is
* represented by the index (td->tune_high_out_start - 1). No return value.
*/
static void tune_low(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
dfll_writel(td, td->tune0_low_voltage_range, DFLL_TUNE0);
dfll_wmb(td);
}
/**
* tune_high - fine-tune DFLL and CPU clock shaper for high voltages
* @pdev: DFLL instance
* @state: tune_state to change the internal state variable to
*
* Fine-tune the DFLL oscillator parameters and the CPU clock shaper
* for the high-voltage range. The bottom end of the high-voltage
* range is represented by the index td->tune_high_out_start.
* Used in closed-loop mode. No return value.
*/
static void tune_high(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
dfll_writel(td, td->tune0_high_voltage_range, DFLL_TUNE0);
dfll_wmb(td);
}
/**
* tune_to_low_state - switch to the minimum safe non-DFLL voltage
* @pdev: DFLL instance
*
* Set the DFLL's voltage output floor to the minimum safe non-DFLL
* voltage, and tune the DFLL for the low-voltage range. Used before
* switching to open-loop mode. This prevents the DFLL from entering
* open-loop mode with an output voltage below the minimum safe
* non-DFLL voltage.
*/
static void tune_to_low_state(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 out_min;
set_tune_state(pdev, TEGRA_DFLL_TUNE_LOW);
tune_low(pdev);
out_min = get_output_min(pdev);
if (td->lut_min != out_min) {
td->lut_min = out_min;
dfll_load_lut(pdev);
}
}
/**
* set_ol_config - prepare to switch to open-loop mode
* @pdev: DFLL instance
*
* Prepare to switch the DFLL to open-loop mode. This involves ensuring
* that the DFLL is in the low-voltage tuning regime, and disabling
* output frequency scaling. No return value.
*/
static void set_ol_config(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
/* always tune low (safe) in open loop */
if (td->tune_state != TEGRA_DFLL_TUNE_LOW)
tune_to_low_state(pdev);
/* 1:1 scaling in open loop */
val = dfll_readl(td, DFLL_FREQ_REQ);
val |= (SCALE_MAX - 1) << DFLL_FREQ_REQ_SCALE_SHIFT;
val &= ~DFLL_FREQ_REQ_FORCE_ENABLE;
dfll_writel(td, val, DFLL_FREQ_REQ);
}
/**
* set_cl_config - prepare to switch to closed-loop mode
* @pdev: DFLL instance
* @req: requested output rate
*
* Prepare to switch the DFLL to closed-loop mode. This involves
* switching the DFLL's tuning voltage regime (if necessary), and
* rewriting the LUT to restrict the minimum and maximum voltages. No
* return value.
*/
static void set_cl_config(struct platform_device *pdev,
struct dfll_rate_req *req)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 out_max, out_min;
u8 out_cap = get_output_cap(pdev, req);
switch (td->tune_state) {
case TEGRA_DFLL_TUNE_LOW:
if (out_cap > td->tune_high_out_start) {
set_tune_state(pdev, TEGRA_DFLL_TUNE_WAIT_DFLL);
mod_timer(&td->tune_timer, jiffies + td->tune_delay);
}
break;
case TEGRA_DFLL_TUNE_HIGH:
case TEGRA_DFLL_TUNE_WAIT_DFLL:
case TEGRA_DFLL_TUNE_WAIT_PMIC:
if (out_cap <= td->tune_high_out_start) {
set_tune_state(pdev, TEGRA_DFLL_TUNE_LOW);
tune_low(pdev);
}
break;
default:
BUG();
}
out_min = get_output_min(pdev);
if (out_cap > out_min + 1)
req->output = out_cap - 1;
else
req->output = out_min + 1;
if (req->output == td->safe_output)
req->output++;
out_max = max((u8)(req->output + 1), td->minimax_output);
if ((td->lut_min != out_min) || (td->lut_max != out_max)) {
td->lut_min = out_min;
td->lut_max = out_max;
dfll_load_lut(pdev);
}
}
/**
* tune_timer_cb - timer callback during TUNE_HIGH_REQUEST
* @data: struct platform_device * of the DFLL instance
*
* Timer callback, used when switching from TUNE_LOW to TUNE_HIGH in
* closed-loop mode. Waits for DFLL I2C voltage command output to
* reach tune_high_out_min, then waits for the PMIC to react to the
* command. No return value.
*/
static void tune_timer_cb(unsigned long data)
{
struct platform_device *pdev = (struct platform_device *)data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val, out_min, out_last;
unsigned long flags;
spin_lock_irqsave(&td->lock, flags);
if (td->tune_state == TEGRA_DFLL_TUNE_WAIT_DFLL) {
out_min = td->lut_min;
val = dfll_readl(td, DFLL_I2C_STS);
out_last = (val >> DFLL_I2C_STS_I2C_LAST_SHIFT) & OUT_MASK;
if (!is_output_i2c_req_pending(pdev) &&
(out_last >= td->tune_high_out_min) &&
(out_min >= td->tune_high_out_min)) {
set_tune_state(pdev, TEGRA_DFLL_TUNE_WAIT_PMIC);
mod_timer(&td->tune_timer, jiffies +
usecs_to_jiffies(DFLL_OUTPUT_RAMP_DELAY));
} else {
mod_timer(&td->tune_timer, jiffies + td->tune_delay);
}
} else if (td->tune_state == TEGRA_DFLL_TUNE_WAIT_PMIC) {
set_tune_state(pdev, TEGRA_DFLL_TUNE_HIGH);
tune_high(pdev);
}
spin_unlock_irqrestore(&td->lock, flags);
}
/**
* tegra_dfll_calibrate - recalibrate dvco_rate_min to match reality
* @pdev: DFLL instance
*
* Adjust our estimate of the DVCO minimum rate based on the measured
* rate. This is only done if the DFLL is in closed loop mode, the
* last request engaged the clock skipper, a minimum amount of time
* has passed since the last calibration attempt, and there is no I2C
* transaction pending. No return value.
*/
static void tegra_dfll_calibrate(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
ktime_t now;
unsigned long data;
u8 out_min;
if ((td->mode != TEGRA_DFLL_CLOSED_LOOP) ||
(td->last_req.rate > td->dvco_rate_min))
return;
now = ktime_get();
if (ktime_us_delta(now, td->last_calibration) < DFLL_CALIBR_TIME)
return;
out_min = get_output_min(pdev);
/* Skip calibration this time if I2C transaction is pending */
val = dfll_readl(td, DFLL_I2C_STS);
if (is_output_i2c_req_pending(pdev)) {
mod_timer(&td->calibration_timer,
jiffies + td->calibration_delay);
return;
}
/* XXX Cache DFLL_MONITOR_CTRL to avoid an unnecessary DFLL read */
if (dfll_readl(td, DFLL_MONITOR_CTRL) != DFLL_MONITOR_CTRL_FREQ)
dfll_writel(td, DFLL_MONITOR_CTRL_FREQ, DFLL_MONITOR_CTRL);
/* Synchronize with sample period, and get rate measurements */
data = dfll_readl(td, DFLL_MONITOR_DATA);
do {
data = dfll_readl(td, DFLL_MONITOR_DATA);
} while (!(data & DFLL_MONITOR_DATA_NEW_MASK));
do {
data = dfll_readl(td, DFLL_MONITOR_DATA);
} while (!(data & DFLL_MONITOR_DATA_NEW_MASK));
td->last_calibration = now;
/* Adjust minimum rate */
data &= DFLL_MONITOR_DATA_VAL_MASK;
data = GET_MONITORED_RATE(data, td->ref_rate);
if ((val > out_min) || (data < (td->dvco_rate_min - RATE_STEP(td))))
td->dvco_rate_min -= RATE_STEP(td);
else if (data > (td->dvco_rate_min + RATE_STEP(td)))
td->dvco_rate_min += RATE_STEP(td);
else
return;
td->dvco_rate_min = clamp(td->dvco_rate_min, td->calibration_range_min,
td->calibration_range_max);
mod_timer(&td->calibration_timer, jiffies + td->calibration_delay);
dev_dbg(&pdev->dev, "calibrated dvco_rate_min %lu\n",
td->dvco_rate_min);
}
/**
* calibration_timer_cb - calibrate the DFLL; called from a timer
* @data: struct platform_device * of the DFLL instance
*
* Take the DFLL lock and run the calibration process. Called as a
* timer callback. No return value.
*/
static void calibration_timer_cb(unsigned long data)
{
struct platform_device *pdev = (struct platform_device *)data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
unsigned long flags;
spin_lock_irqsave(&td->lock, flags);
tegra_dfll_calibrate(pdev);
spin_unlock_irqrestore(&td->lock, flags);
}
/**
* set_request - ask the DFLL to tune to a different frequency
* @pdev: DFLL instance
* @req: target frequency request
*
* Tell the DFLL to try to change its output frequency to the
* frequency represented by @req. DFLL must be in closed-loop mode.
* No return value.
*/
static void set_request(struct platform_device *pdev, struct dfll_rate_req *req)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
int force_val = req->output - td->safe_output;
int coef = 128; /* FIXME: td->cg_scale? */;
force_val = force_val * coef / td->cg;
force_val = clamp(force_val, FORCE_MIN, FORCE_MAX);
val = req->freq << DFLL_FREQ_REQ_FREQ_SHIFT;
val |= req->scale << DFLL_FREQ_REQ_SCALE_SHIFT;
val |= ((u32)force_val << DFLL_FREQ_REQ_FORCE_SHIFT) &
DFLL_FREQ_REQ_FORCE_MASK;
val |= DFLL_FREQ_REQ_FREQ_VALID | DFLL_FREQ_REQ_FORCE_ENABLE;
dfll_writel(td, val, DFLL_FREQ_REQ);
dfll_wmb(td);
}
/**
* find_mv_out_cap - find the out_map index with voltage >= @mv
* @pdev: DFLL instance
* @mv: millivolts
*
* Find the out_map index with voltage greater than or equal to @mv,
* and return it. If all of the voltages in out_map are less than
* @mv, then return the out_map index * corresponding to the highest
* possible voltage, even though it's less than @mv.
*/
static u8 find_mv_out_cap(struct platform_device *pdev, int mv)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u8 cap;
for (cap = 0; cap < td->dvfs_info->num_voltages; cap++)
if (td->out_map[cap]->reg_uv >= mv * 1000)
return cap;
return cap - 1; /* maximum possible output */
}
/**
* find_mv_out_floor - find the largest out_map index with voltage < @mv
* @pdev: DFLL instance
* @mv: millivolts
*
* Find the largest out_map index with voltage lesser to @mv,
* and return it. If all of the voltages in out_map are greater than
* @mv, then return the out_map index * corresponding to the minimum
* possible voltage, even though it's greater than @mv.
*/
static u8 find_mv_out_floor(struct platform_device *pdev, int mv)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u8 floor;
for (floor = 0; floor < td->dvfs_info->num_voltages; floor++) {
if (td->out_map[floor]->reg_uv > mv * 1000) {
if (!floor)
/* minimum possible output */
return 0;
break;
}
}
return floor - 1;
}
/**
* find_safe_output - find minimum safe voltage for a desired clock frequency
* @pdev: DFLL instance
* @rate: desired clock frequency
* @safe_output: ptr to a variable to return the minimum safe voltage index
*
* For a desired clock frequency @rate, finds the voltage @safe_output
* that is unconditionally safe to run at for any PVT point. There is
* guaranteed to be some voltage below @safe_output that the DFLL can
* adjust down to. Returns 0 if found, or -ENOENT if no safe voltage
* could be found for @rate.
*/
static int find_safe_output(struct platform_device *pdev, unsigned long rate,
u8 *safe_output)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i;
for (i = 0; i < td->dvfs_info->num_freqs; i++) {
if (td->dvfs_info->freqs[i] >= rate) {
*safe_output = td->dvfs_info->clk_dvfs_map[i];
return 0;
}
}
return -ENOENT;
}
/**
* find_dvco_rate_min - find min output freq for an output voltage LUT index
* @pdev: DFLL instance
* @out_min: an output voltage LUT index to find the minimum frequency for
*
* Given an output voltage LUT index @out_min, return the "minimum" clock
* frequency that the DVCO will generate (based on the CVB table data).
* Note that this frequency may not really be a strict minimum, due to
* calibration/characterization errors.
*/
static unsigned long find_dvco_rate_min(struct platform_device *pdev,
u8 out_min)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i;
for (i = 0; i < td->dvfs_info->num_freqs; i++)
if (td->dvfs_info->clk_dvfs_map[i] > out_min)
break;
i = i ? i-1 : 0;
return td->dvfs_info->freqs[i];
}
/**
* set_dvco_rate_min - set the minimum DVCO output rate & interval
* @pdev: DFLL instance
*
* Find and cache the "minimum" DVCO output frequency, as well as the
* lower and upper calibration boundaries around this frequency. No
* return value.
*/
static void set_dvco_rate_min(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
unsigned long rate = td->out_rate_min;
if (td->therm_floors_idx < td->therm_floors_num)
rate = find_dvco_rate_min(
pdev, td->therm_floors[td->therm_floors_idx].output);
/* round minimum rate to request unit (ref_rate/2) boundary */
td->dvco_rate_min = ROUND_MIN_RATE(rate, td->ref_rate);
/* dvco min rate is under-estimated - skewed range up */
td->calibration_range_min = td->dvco_rate_min - 2 * RATE_STEP(td);
td->calibration_range_max = td->dvco_rate_min + 8 * RATE_STEP(td);
}
/**
* get_cvb_voltage - returns the CVB voltage based on frequency & silicon
* @pdev: DFLL instance
* @c0: calibration data 0 (see below)
* @c1: calibration data 1 (see below)
* @c2: calibration data 2 (see below)
*
* Return the CVB voltage (in millivolts) for a particular set of
* frequency and silicon parameters. The formula used to compute the
* voltage is:
*
* cvb_mv = ((c2 * speedo / s_scale + c1) * speedo / s_scale + c0) / v_scale
*/
static int get_cvb_voltage(struct platform_device *pdev, int c0, int c1,
int c2)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
/* apply only speedo scale: output mv = cvb_mv * v_scale */
int mv;
/* combined: apply voltage scale and round to cvb alignment step */
mv = DIV_ROUND_CLOSEST(c2 * td->speedo_value, td->cvb_speedo_scale);
mv = DIV_ROUND_CLOSEST((mv + c1) * td->speedo_value,
td->cvb_speedo_scale) + c0;
return DIV_ROUND_UP(mv * 1000,
td->cvb_voltage_scale * td->vdd_step) *
td->vdd_step / 1000;
}
/**
* find_vdd_map_entry_exact - find vdd_map entry with voltage equal to @mv
* @pdev: DFLL instance
* @mv: millivolts to look up in vdd_map[]
*
* Attempt to find the entry in the vdd_map with the voltage is equal
* to @mv. Returns a pointer to the matching vdd_map entry if found;
* otherwise, returns NULL.
*/
static struct tegra_dfll_voltage_reg_map *find_vdd_map_entry_exact(
struct platform_device *pdev, int mv)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i;
for (i = 0; i < td->vdd_map_size; i++)
if (mv == td->vdd_map[i].reg_uv / 1000)
return &td->vdd_map[i];
return NULL;
}
/**
* find_vdd_map_entry_min - find first vdd_map entry with voltage >= @mv
* @pdev: DFLL instance
* @mv: millivolts to look up in vdd_map[]
*
* Attempt to find the lowest-voltage entry in the vdd_map with a
* voltage equal to or greater than @mv. Returns a pointer to the
* matching vdd_map entry if found; otherwise, returns NULL.
*/
static struct tegra_dfll_voltage_reg_map *find_vdd_map_entry_min(
struct platform_device *pdev, int mv)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i;
for (i = 0; i < td->vdd_map_size; i++)
if (mv <= td->vdd_map[i].reg_uv / 1000)
return &td->vdd_map[i];
return NULL;
}
/**
* prepare_volt_freq_table - analyze the CVB table and build indexes
* @pdev: DFLL instance
*
* Loop through the CVB table data, generating the starting output
* voltage for each frequency point. (Each CVB entry specifies CPU
* frequency and CVB coefficients to calculate the respective voltage
* when DFLL is used as the CPU clock source.)
*
* Minimum voltage limit is applied only to DFLL source. Maximum
* voltage limit is applied by directly clipping voltage for DFLL.
*
* Returns 0 upon success, or -EINVAL if the CVB table or DFLL configuration
* data is invalid.
*/
static int prepare_volt_freq_table(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct tegra_dfll_cvb *tdc;
u32 dfll_mv;
int i;
unsigned long fmax_at_vmin = 0;
for (i = 0; i < td->dvfs_info->cvb_table_len; i++) {
tdc = &td->dvfs_info->cvb_table[i];
dfll_mv = get_cvb_voltage(pdev, tdc->c0, tdc->c1, tdc->c2);
/* Check maximum frequency at minimum voltage */
if (dfll_mv > td->min_millivolts) {
if (!i)
break; /* 1st entry already above Vmin */
if (!fmax_at_vmin)
fmax_at_vmin = td->dvfs_info->freqs[i - 1];
}
dfll_mv = max(dfll_mv, td->min_millivolts);
td->dvfs_info->freqs[i] = tdc->freq;
td->dvfs_info->cpu_dfll_millivolts[i] = min(dfll_mv,
td->cvb_max_millivolts);
td->dvfs_info->num_freqs = i + 1;
/*
* "Round-up" frequency list cut-off (keep first entry that
* exceeds max voltage - the voltage limit will be enforced
* anyway, so when requested this frequency dfll will settle
* at whatever high frequency it can on the particular chip)
*/
if (dfll_mv > td->cvb_max_millivolts)
break;
}
/*
* Table must not be empty and must have at least
* 1. one entry <= Vmin and
* 2. one entry >= Vmin
*/
if (!i || !fmax_at_vmin) {
dev_err(&pdev->dev, "invalid cpu dvfs table\n");
return -EINVAL;
}
td->out_rate_min = fmax_at_vmin;
return 0;
}
/**
* dfll_init_maps - build output voltage-related maps
* @pdev: DFLL instance
*
* Build the LUT index-to-output PMIC voltage value map (out_map), and
* the CVB table index-to-LUT index map (clk_dvfs_map). Returns 0
* upon success or -EINVAL, -ERANGE, or -ENOENT upon error.
*/
static int __must_check dfll_init_maps(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i, j, v, v_max, n, r;
struct tegra_dfll_voltage_reg_map *m;
BUILD_BUG_ON(MAX_DFLL_VOLTAGES > OUT_MASK + 1);
r = prepare_volt_freq_table(pdev);
if (r) {
dev_err(&pdev->dev, "unable to prepare voltage table\n");
return -EINVAL;
}
n = td->dvfs_info->num_freqs;
if (n >= MAX_DFLL_VOLTAGES) {
dev_err(&pdev->dev, "too many frequencies (%d)\n", n);
return -EINVAL;
}
v_max = td->dvfs_info->cpu_dfll_millivolts[n - 1];
v = td->min_millivolts;
if (v > td->dvfs_info->cpu_dfll_millivolts[0]) {
dev_err(&pdev->dev,
"min voltage %d > lowest voltage in map %d\n",
td->min_millivolts,
td->dvfs_info->cpu_dfll_millivolts[0]);
return -ERANGE;
}
td->out_map[0] = find_vdd_map_entry_exact(pdev, v);
if (!td->out_map[0]) {
dev_err(&pdev->dev, "no vdd_map entry for min voltage %d\n", v);
return -ENOENT;
}
for (i = 0, j = 1; i < n; i++) {
for (;;) {
v += max(1, (v_max - v) / (MAX_DFLL_VOLTAGES - j));
if (v >= td->dvfs_info->cpu_dfll_millivolts[i])
break;
m = find_vdd_map_entry_min(pdev, v);
if (!m) {
dev_err(&pdev->dev,
"no vdd_map entry for map voltage %d\n",
v);
return -ENOENT;
}
if (m != td->out_map[j - 1])
td->out_map[j++] = m;
}
v = td->dvfs_info->cpu_dfll_millivolts[i];
m = find_vdd_map_entry_exact(pdev, v);
if (!m) {
dev_err(&pdev->dev,
"no exact vdd_map entry for voltage %d\n", v);
return -ENOENT;
}
if (m != td->out_map[j - 1])
td->out_map[j++] = m;
td->dvfs_info->clk_dvfs_map[i] = j - 1;
if (j > MAX_DFLL_VOLTAGES) {
dev_err(&pdev->dev, "too many out_map entries\n");
return -ENOMEM;
}
}
td->dvfs_info->num_voltages = j;
return 0;
}
/**
* dfll_init_tuning_thresholds - set up the high voltage range, if possible
* @pdev: DFLL instance
*
* Determine whether the DFLL tuning parameters need to be
* reprogrammed when the DFLL voltage reaches a certain minimum
* threshold. No return value.
*/
static void dfll_init_tuning_thresholds(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u8 out_min, out_start, max_voltage_index;
max_voltage_index = td->dvfs_info->num_voltages - 1;
/*
* Convert high tuning voltage threshold into output LUT
* index, and add necessary margin. If voltage threshold is
* outside operating range set it at maximum output level to
* effectively disable tuning parameters adjustment.
*/
td->tune_high_out_min = max_voltage_index;
td->tune_high_out_start = max_voltage_index;
if (td->tune_high_min_mv < td->min_millivolts)
return; /* no difference between low & high voltage range */
out_min = find_mv_out_cap(pdev, td->tune_high_min_mv);
if ((out_min + 2) > max_voltage_index)
return;
out_start = out_min + DFLL_TUNE_HIGH_MARGIN_STEPS;
if (out_start > max_voltage_index)
return;
td->tune_high_out_min = out_min;
td->tune_high_out_start = out_start;
if (td->minimax_output <= out_min)
td->minimax_output = out_min + 1;
}
/**
* dfll_init_hot_output_cap - set a thermally-sensitive maximum voltage cap
* @pdev: DFLL instance
*
* During DFLL driver initialization, map the temperature-dependent
* voltage caps from millivolts to output LUT indexes, and make sure
* there is room for regulation below the minimum thermal cap. Must
* be called after the therm-caps property has been parsed from DT.
* Must be called after td->safe_output is first set by taking
* td->therm_caps[0] into consideration. The voltage cap must
* monotonically decrease as temperatures monotonically increase.
* Returns 0 upon success, -EINVAL if the voltage cap data is in an
* invalid format, or -ENOSPC if there's no space to adjust the
* voltage above the minimum.
*/
static int __must_check dfll_init_hot_output_cap(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i;
u8 output;
if (td->therm_caps_num == 0)
return 0;
for (i = 0; i < td->therm_caps_num; i++) {
if (i > 0 &&
(td->therm_caps[i].mv >= td->therm_caps[i-1].mv ||
td->therm_caps[i].temp <= td->therm_caps[i-1].temp)) {
dev_err(&pdev->dev,
"format error in thermal cap data\n");
return -EINVAL;
}
td->therm_caps[i].output =
find_mv_out_floor(pdev, td->therm_caps[i].mv);
}
output = td->therm_caps[td->therm_caps_num - 1].output;
if (output < td->minimax_output) {
dev_err(&pdev->dev,
"no space available for regulation below max\n");
return -ENOSPC;
}
return 0;
}
/**
* dfll_init_cold_output_floor - set a thermally-sensitive minimum voltage floor
* @pdev: DFLL instance
*
* During DFLL driver initialization, map the temperature-dependent
* voltage floors from millivolts to output LUT indexes, and make sure
* there is room for regulation above the maximum thermal floor. Must
* be called after the therm-floors property has been parsed from DT.
* Must be called before td->safe_output is first set by taking
* td->therm_floors[0] into consideration. The voltage floor must
* monotonically decrease as temperatures monotonically increase.
* Returns 0 upon success, -EINVAL if the voltage floor data is in an
* invalid format, or -ENOSPC if there's no space to adjust the
* voltage above the minimum.
*/
static int __must_check dfll_init_cold_output_floor(
struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int i;
if (td->therm_floors_num == 0)
return 0;
for (i = 0; i < td->therm_floors_num; i++) {
if (i > 0 &&
(td->therm_floors[i].mv >= td->therm_floors[i-1].mv ||
td->therm_floors[i].temp <= td->therm_floors[i-1].temp)) {
dev_err(&pdev->dev,
"format error in thermal floor data\n");
return -EINVAL;
}
td->therm_floors[i].output =
find_mv_out_cap(pdev, td->therm_floors[i].mv);
}
if (td->therm_floors[0].output + 2 >= td->dvfs_info->num_voltages) {
dev_err(&pdev->dev,
"no space available for regulation above min\n");
return -ENOSPC;
}
return 0;
}
/**
* dfll_init_output_thresholds - set various DFLL min/max voltage thresholds
* @pdev: DFLL instance
*
* During DFLL driver initialization, set the minimum safe voltage
* output LUT index (safe_output), along with the maximum voltage that
* the DFLL can request in closed-loop mode (minimax_output). Returns
* 0 upon success, or passes along the return value from
* dfll_init_cold_output_floor() upon error.
*/
static int __must_check dfll_init_output_thresholds(
struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int r;
td->minimax_output = 0;
td->tune_state = TEGRA_DFLL_TUNE_LOW;
dfll_init_tuning_thresholds(pdev);
r = dfll_init_cold_output_floor(pdev);
if (r) {
dev_err(&pdev->dev, "could not set cold output floor: %d\n", r);
return r;
}
/* make sure safe output is safe at any temperature */
td->safe_output = td->therm_floors[0].output ? : 1;
if (td->minimax_output <= td->safe_output)
td->minimax_output = td->safe_output + 1;
/* init caps after minimax output is determined */
r = dfll_init_hot_output_cap(pdev);
if (r) {
dev_err(&pdev->dev, "could not set hot output cap: %d\n", r);
return r;
}
return 0;
}
/*
* DFLL PMIC I2C controller
*/
/**
* dfll_init_i2c_clk - initialize clock for the DFLL's internal I2C controller
* @pdev: DFLL instance
*
* During DFLL driver initialization, set up the DFLL's internal I2C
* controller. Only used if the DFLL is using I2C to communicate with
* the PMIC (which is the only method currently supported in this
* driver). td->i2c_clk must be enabled before calling. Returns 0
* upon success or -ERANGE if the computed I2C divisor is out of
* range.
*/
static int dfll_init_i2c_clk(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
u32 div = 2; /* default hs divisor just in case */
unsigned long i2c_clk_rate;
i2c_clk_rate = clk_get_rate(td->i2c_clk);
val = GET_DIV(i2c_clk_rate, td->pmic_i2c_fs_rate,
DFLL_I2C_CLK_DIVISOR_PREDIV);
if (val == 0 || val > DFLL_I2C_CLK_DIVISOR_MASK) {
dev_err(&pdev->dev, "I2C FS clock divisor out of range\n");
return -ERANGE;
}
val = (val - 1) << DFLL_I2C_CLK_DIVISOR_FS_SHIFT;
if (td->pmic_i2c_hs_rate) {
div = GET_DIV(i2c_clk_rate, td->pmic_i2c_hs_rate,
DFLL_I2C_CLK_DIVISOR_HSMODE_PREDIV);
if (div == 0 || div > DFLL_I2C_CLK_DIVISOR_MASK) {
dev_err(&pdev->dev,
"I2C HS clock divisor out of range\n");
return -ERANGE;
}
}
val |= (div - 1) << DFLL_I2C_CLK_DIVISOR_HS_SHIFT;
dfll_writel(td, val, DFLL_I2C_CLK_DIVISOR);
dfll_wmb(td);
return 0;
}
/*
* DFLL-to-I2C controller interface
*/
/**
* dfll_init_i2c_if - set up the DFLL's DFLL-I2C interface
* @pdev: DFLL instance
*
* During DFLL driver initialization, program the DFLL-I2C interfae
* with the PMU slave address, vdd register offset, and transfer mode.
* This data is used by the DFLL to automatically construct I2C
* voltage-set commands, which are then passed to the DFLL's internal
* I2C controller. No return value.
*/
static void dfll_init_i2c_if(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
val = td->pmic_i2c_addr << DFLL_I2C_CFG_SLAVE_ADDR_SHIFT;
if (td->pmic_i2c_ten_bit_addrs)
val |= DFLL_I2C_CFG_SLAVE_ADDR_10;
if (td->pmic_i2c_hs_rate) {
val |= (td->pmic_i2c_hs_master_code <<
DFLL_I2C_CFG_HS_CODE_SHIFT);
val |= DFLL_I2C_CFG_PACKET_ENABLE;
}
val |= DFLL_I2C_CFG_SIZE_MASK;
val |= DFLL_I2C_CFG_ARB_ENABLE;
dfll_writel(td, val, DFLL_I2C_CFG);
dfll_writel(td, td->pmic_i2c_voltage_reg, DFLL_I2C_VDD_REG_ADDR);
}
/**
* dfll_init_out_if - prepare DFLL-to-PMIC interface
* @pdev: DFLL instance
*
* During DFLL driver initialization or resume from context loss,
* disable the I2C command output to the PMIC, set safe voltage and
* output limits, and disable and clear limit interrupts. No return
* value.
*/
static void dfll_init_out_if(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val, out_min, out_max;
/* Necessary for the call to get_output_min() */
td->tune_state = TEGRA_DFLL_TUNE_LOW;
td->therm_caps_idx = td->therm_caps_num;
td->therm_floors_idx = 0;
set_dvco_rate_min(pdev);
/*
* Allow the entire range of LUT indexes, but limit output
* voltage in LUT mapping (this "indirect" application of
* limits is used, because h/w does not support dynamic change
* of index limits, but dynamic reload of LUT is fine).
*/
out_min = 0;
out_max = td->dvfs_info->num_voltages - 1;
td->lut_min = get_output_min(pdev);
td->lut_max = get_output_cap(pdev, NULL);
val = (td->safe_output << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
(out_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
(out_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
dfll_writel(td, val, DFLL_OUTPUT_CFG);
dfll_wmb(td);
dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
dfll_writel(td, 0, DFLL_INTR_EN);
dfll_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK, DFLL_INTR_STS);
dfll_load_lut(pdev);
dfll_init_i2c_if(pdev);
dfll_init_i2c_clk(pdev);
}
/**
* dfll_init_cntrl_logic - set up some of the DFLL registers
* @pdev: DFLL instance
*
* During DFLL driver initialization or resume from context loss, set
* up many of the DFLL IP block registers, including the DFLL mode,
* control loop parameters, and tuning. Returns 0 upon success or
* -ERANGE if a value destined for a register bitfield will exceed
* that bitfield (which indicates a problem with the DT data).
*/
static int __must_check dfll_init_cntrl_logic(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
set_mode(pdev, TEGRA_DFLL_DISABLED);
val = GET_DIV(td->ref_rate, td->sample_rate, DFLL_CONFIG_DIV_PRESCALE);
if (val > DFLL_CONFIG_DIV_MASK) {
dev_err(&pdev->dev, "sample-rate exceeds bitfield width\n");
return -ERANGE;
}
dfll_writel(td, val, DFLL_CONFIG);
val = (td->force_mode << DFLL_PARAMS_FORCE_MODE_SHIFT) |
(td->cf << DFLL_PARAMS_CF_PARAM_SHIFT) |
(td->ci << DFLL_PARAMS_CI_PARAM_SHIFT) |
((u8)td->cg << DFLL_PARAMS_CG_PARAM_SHIFT) |
(td->cg_scale << DFLL_PARAMS_CG_SCALE_SHIFT);
dfll_writel(td, val, DFLL_PARAMS);
tune_low(pdev);
dfll_writel(td, td->tune1, DFLL_TUNE1);
/* configure droop (skipper 1) and scale (skipper 2) */
val = GET_DROOP_FREQ(td->droop_rate_min, td->ref_rate);
val <<= DFLL_DROOP_CTRL_MIN_FREQ_SHIFT;
if (val > DFLL_DROOP_CTRL_MIN_FREQ_MASK) {
dev_err(&pdev->dev, "droop-rate-min exceeds bitfield width\n");
return -ERANGE;
}
val |= (td->droop_cut_value << DFLL_DROOP_CTRL_CUT_SHIFT);
val |= (td->droop_restore_ramp << DFLL_DROOP_CTRL_RAMP_SHIFT);
dfll_writel(td, val, DFLL_DROOP_CTRL);
td->last_req.cap = 0;
td->last_req.freq = 0;
td->last_req.output = 0;
td->last_req.scale = SCALE_MAX - 1;
dfll_writel(td, DFLL_FREQ_REQ_SCALE_MASK, DFLL_FREQ_REQ);
dfll_writel(td, td->scale_out_ramp, DFLL_SCALE_RAMP);
/* select frequency for monitoring */
dfll_writel(td, DFLL_MONITOR_CTRL_FREQ, DFLL_MONITOR_CTRL);
dfll_wmb(td);
return 0;
}
/**
* dfll_enable_clocks - enable all clocks needed by the DFLL
* @pdev: DFLL instance
*
* Enable all clocks needed by the DFLL. Assumes that clk_prepare()
* has already been called on all the clocks. Assumes that the DFLL
* communicates with the PMIC via the DFLL I2C interface, which is
* currently the only option supported by this driver. No return
* value.
*/
static void dfll_enable_clocks(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
clk_enable(td->i2c_clk);
clk_enable(td->ref_clk);
clk_enable(td->soc_clk);
}
/**
* dfll_disable_clocks - disable all clocks needed by the DFLL
* @pdev: DFLL instance
*
* Disable all clocks needed by the DFLL. Assumes that other code
* will later call clk_unprepare(). Assumes that the DFLL
* communicates with the PMIC via the DFLL I2C interface, which is
* currently the only option supported by this driver. No return
* value.
*/
/* XXX: Shouldn't the clock ordering be reversed from the enable? */
static void dfll_disable_clocks(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
clk_disable(td->i2c_clk);
clk_disable(td->ref_clk);
clk_disable(td->soc_clk);
}
/**
* dfll_init_timers - prepare the TUNE_HIGH and calibration timers
* @pdev: DFLL instance
*
* Fill in the required fields for the TUNE_HIGH and calibration
* timers. The TUNE_HIGH timer is used in DFLL configurations with
* both low and high voltage tuning ranges. The timer callback is
* activated upon a request to switch to the high voltage tuning range
* - see set_cl_config(). The calibration timer is used to adjust
* the DVCO minimum rate. No return value.
*/
static void dfll_init_timers(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
init_timer(&td->tune_timer);
td->tune_timer.function = tune_timer_cb;
td->tune_timer.data = (unsigned long)pdev;
td->tune_delay = usecs_to_jiffies(DFLL_TUNE_HIGH_DELAY);
init_timer_deferrable(&td->calibration_timer);
td->calibration_timer.function = calibration_timer_cb;
td->calibration_timer.data = (unsigned long)pdev;
td->calibration_delay = usecs_to_jiffies(DFLL_CALIBR_TIME);
}
/**
* dfll_init - Prepare the DFLL IP block for use
* @pdev: DFLL instance
*
* Do everything necessary to prepare the DFLL IP block for use.
* The DFLL will be left in DISABLED state. Called by
* tegra_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;
tegra124_clock_deassert_dfll_dvco_reset();
ret = clk_prepare_enable(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_enable(td->ref_clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable ref_clk\n");
goto di_err1;
}
ret = clk_prepare_enable(td->soc_clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable soc_clk\n");
goto di_err2;
}
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");
goto di_err3;
}
dfll_init_timers(pdev);
ret = dfll_init_maps(pdev);
if (ret) {
dev_err(&pdev->dev, "could not init the voltage map\n");
goto di_err3;
}
ret = dfll_init_output_thresholds(pdev);
if (ret) {
dev_err(&pdev->dev, "could not init the voltage thresholds\n");
goto di_err3;
}
dfll_init_out_if(pdev);
/* Configure control registers in disabled mode */
ret = dfll_init_cntrl_logic(pdev);
if (ret) {
dev_err(&pdev->dev, "could not init the control logic\n");
goto di_err3;
}
spin_lock_init(&td->lock);
dfll_disable_clocks(pdev);
return 0;
di_err3:
clk_disable_unprepare(td->soc_clk);
di_err2:
clk_disable_unprepare(td->ref_clk);
di_err1:
clk_disable_unprepare(td->i2c_clk);
tegra124_clock_assert_dfll_dvco_reset();
return ret;
}
/*
* DFLL enable/disable & open-loop <-> closed-loop transitions
*/
/**
* tegra_dfll_disable - switch from open-loop mode to disabled mode
* @pdev: DFLL instance
*
* Switch from OPEN_LOOP state to DISABLED state. No return value.
*/
static void tegra_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 != TEGRA_DFLL_OPEN_LOOP) {
dev_err(&pdev->dev, "cannot disable DFLL in %s mode\n",
mode_name[td->mode]);
goto tdd_exit;
}
set_mode(pdev, TEGRA_DFLL_DISABLED);
tdd_exit:
spin_unlock_irqrestore(&td->lock, flags);
}
/**
* tegra_dfll_enable - switch a disabled DFLL to open-loop mode
* @pdev: DFLL instance
*
* 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 tegra_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 != TEGRA_DFLL_DISABLED) {
dev_err(&pdev->dev, "cannot enable DFLL in %s mode\n",
mode_name[td->mode]);
ret = -EPERM;
goto tde_exit;
}
set_mode(pdev, TEGRA_DFLL_OPEN_LOOP);
tde_exit:
spin_unlock_irqrestore(&td->lock, flags);
return ret;
}
/**
* tegra_dfll_lock - switch from open-loop to closed-loop mode
* @pdev: DFLL instance
*
* Switch from OPEN_LOOP state to CLOSED_LOOP state. Returns 0 upon success,
* -EINVAL if the DFLL's target rate hasn't been set yet, or -EPERM if the
* DFLL is not currently in open-loop mode.
*/
static int tegra_dfll_lock(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct dfll_rate_req *req = &td->last_req;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&td->lock, flags);
if (td->mode != TEGRA_DFLL_OPEN_LOOP) {
WARN(1, "DFLL: cannot lock in %s mode\n", mode_name[td->mode]);
ret = -EPERM;
goto tdl_exit;
}
if (req->freq == 0) {
dev_err(&pdev->dev, "cannot lock DFLL at rate 0\n");
ret = -EINVAL;
goto tdl_exit;
}
/*
* Update control logic setting with last rate request; sync
* output limits with current tuning and thermal state, enable
* output and switch to closed loop mode.
*/
set_cl_config(pdev, req);
i2c_output_enable(pdev);
set_mode(pdev, TEGRA_DFLL_CLOSED_LOOP);
set_request(pdev, req);
mod_timer(&td->calibration_timer, jiffies + td->calibration_delay);
tdl_exit:
spin_unlock_irqrestore(&td->lock, flags);
return ret;
}
/**
* tegra_dfll_unlock - switch from closed-loop to open-loop mode
* @pdev: DFLL instance
*
* Switch from CLOSED_LOOP state to OPEN_LOOP state. Passes along the
* return value from i2c_output_disable_flush(), or -EPERM if the
* DFLL is not currently in closed-loop mode.
*/
static int tegra_dfll_unlock(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
unsigned long flags;
int ret;
spin_lock_irqsave(&td->lock, flags);
if (td->mode != TEGRA_DFLL_CLOSED_LOOP) {
WARN(1, "DFLL: cannot unlock in %s mode\n",
mode_name[td->mode]);
ret = -EPERM;
goto tdu_exit;
}
set_ol_config(pdev);
ret = i2c_output_disable_flush(pdev);
if (ret)
dev_warn(&pdev->dev, "I2C pending timeout\n");
set_mode(pdev, TEGRA_DFLL_OPEN_LOOP);
tdu_exit:
spin_unlock_irqrestore(&td->lock, flags);
return ret;
}
/*
* Set/get the DFLL's targeted output clock rate
*/
/**
* reprogram_last_request - reprogram the last frequency request in CL mode
* @pdev: DFLL instance
*
* Reprogram the last frequency request when the DFLL is in
* closed-loop mode, changing voltage regimes if necessary. No return
* value.
*/
static void reprogram_last_request(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
if (td->mode != TEGRA_DFLL_CLOSED_LOOP)
return;
set_cl_config(pdev, &td->last_req);
set_request(pdev, &td->last_req);
}
/**
* calc_req_scale_mult - calculate DFLL parameters for a given rate
* @pdev: DFLL instance
* @req: DFLL-rate-request structure record pointer
* @dvco_rate: ptr to the unsigned long containing the DFLL DVCO rate
*
* Populate the DFLL-rate-request record @req fields with the scale
* and freq parameters, based on the target input rate, based in by
* the caller in req->rate. Pass back the approximate rate that the
* DVCO will generate in @dvco_rate. Returns 0 upon success, or
* -EINVAL if the requested rate in req->rate is too high or low for
* the DFLL to generate.
*/
static int calc_req_scale_mult(struct platform_device *pdev,
struct dfll_rate_req *req,
unsigned long *dvco_rate)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
u32 val;
int scale;
unsigned long rate;
rate = req->rate;
/* Determine DFLL output scale */
req->scale = SCALE_MAX - 1;
if (rate < td->dvco_rate_min) {
scale = DIV_ROUND_CLOSEST((rate / 1000 * SCALE_MAX),
(td->dvco_rate_min / 1000));
if (!scale) {
dev_err(&pdev->dev,
"rate %lu is below scalable range\n",
rate);
return -EINVAL;
}
req->scale = scale - 1;
rate = td->dvco_rate_min;
}
/* Convert requested rate into frequency request and scale settings */
val = GET_REQUEST_FREQ(rate, td->ref_rate);
if (val > FREQ_MAX) {
dev_err(&pdev->dev, "rate %lu is above dfll range\n", rate);
return -EINVAL;
}
req->freq = val;
*dvco_rate = GET_REQUEST_RATE(val, td->ref_rate);
return 0;
}
/**
* tegra_dfll_request_rate - set the next rate for the DFLL to tune to
* @pdev: DFLL instance
* @rate: clock rate to target
*
* Convert the requested clock rate @rate into the DFLL control logic
* settings. In closed-loop mode, update new settings immediately to
* adjust DFLL output rate accordingly. Otherwise, just save them
* until the next switch to closed loop. Returns 0 upon success,
* -EPERM if the DFLL driver has not yet been initialized, or -EINVAL
* if @rate is outside the DFLL's tunable range or if there is no safe
* output voltage for @rate.
*/
static int tegra_dfll_request_rate(struct platform_device *pdev,
unsigned long rate)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct dfll_rate_req req;
unsigned long flags, dvco_rate;
int r;
int ret = 0;
spin_lock_irqsave(&td->lock, flags);
req.rate = rate;
if (td->mode == TEGRA_DFLL_UNINITIALIZED) {
dev_err(&pdev->dev, "cannot set DFLL rate in %s mode\n",
mode_name[td->mode]);
ret = -EPERM;
goto tdrr_exit;
}
/* Calibrate dfll minimum rate */
tegra_dfll_calibrate(pdev);
r = calc_req_scale_mult(pdev, &req, &dvco_rate);
if (r)
goto tdrr_exit;
/* Find safe voltage for requested rate */
if (find_safe_output(pdev, dvco_rate, &req.output)) {
dev_err(&pdev->dev, "failed to find safe output for rate %lu\n",
dvco_rate);
ret = -EINVAL;
goto tdrr_exit;
}
req.cap = req.output;
/*
* Save validated request, and in CLOSED_LOOP mode actually update
* control logic settings; use request output to set maximum voltage
* limit, but keep one LUT step room above safe voltage
*/
td->last_req = req;
if (td->mode == TEGRA_DFLL_CLOSED_LOOP)
reprogram_last_request(pdev);
tdrr_exit:
spin_unlock_irqrestore(&td->lock, flags);
return ret;
}
/**
* calc_dfll_request_rate - return the approximate DFLL output clock rate
* @pdev: DFLL instance
* @req: ptr to a DFLL-rate-request structure record
*
* Return the approximate DFLL output clock rate that @req would
* generate, after the scaling skipper.
*/
static unsigned long calc_dfll_request_rate(struct platform_device *pdev,
struct dfll_rate_req *req)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
unsigned long rate;
if ((req->scale + 1) < SCALE_MAX)
rate = (req->rate / 1000) * 1000;
else
rate = GET_REQUEST_RATE(req->freq, td->ref_rate);
return rate;
}
/**
* tegra_dfll_request_get - return the DFLL's target rate from the last request
* @pdev: DFLL instance
*
* If the DFLL is currently in closed-loop mode, return the clock rate
* that the DFLL is targeting. If the DFLL is currently in open-loop
* mode or is disabled, return the clock rate that the DFLL will
* target when it next enters closed-loop mode. The rates come from
* the driver's cache of the last request, not the hardware. Note
* that the actual DFLL output rate may not exactly match the targeted rate,
* and may vary. Returns the rate in Hz.
*/
static unsigned long tegra_dfll_request_get(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct dfll_rate_req *req = &td->last_req;
unsigned long flags;
u32 rate;
spin_lock_irqsave(&td->lock, flags);
rate = calc_dfll_request_rate(pdev, req);
spin_unlock_irqrestore(&td->lock, flags);
return rate;
}
/*
* Clock framework integration
*/
static int tegra_dfll_clk_is_enabled(struct clk_hw *hw)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct tegra_dfll *td = dev_get_drvdata(&tdc->pdev->dev);
return (td->mode == TEGRA_DFLL_OPEN_LOOP ||
td->mode == TEGRA_DFLL_CLOSED_LOOP);
}
/**
* tegra_dfll_clk_prepare - enable the DFLL clocks
* @hw: DFLL instance struct clk_hw *
*
* Enable the DFLL clocks. XXX This is a temporary workaround until
* the clock framework gets multiple-parent or re-entrancy support.
*/
static int tegra_dfll_clk_prepare(struct clk_hw *hw)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct platform_device *pdev = tdc->pdev;
dfll_enable_clocks(pdev);
return 0;
}
/**
* tegra_dfll_clk_unprepare - disable the DFLL clocks
* @hw: DFLL instance struct clk_hw *
*
* Disable the DFLL clocks. XXX This is a temporary workaround until
* the clock framework gets multiple-parent or re-entrancy support.
*/
static void tegra_dfll_clk_unprepare(struct clk_hw *hw)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct platform_device *pdev = tdc->pdev;
/* XXX Validate the current state of the DFLL first */
dfll_disable_clocks(pdev);
}
static int tegra_dfll_clk_enable(struct clk_hw *hw)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct platform_device *pdev = tdc->pdev;
/* XXX Validate the current state of the DFLL first */
return tegra_dfll_enable(pdev);
}
static void tegra_dfll_clk_disable(struct clk_hw *hw)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct platform_device *pdev = tdc->pdev;
/* XXX Validate the current state of the DFLL first */
tegra_dfll_disable(pdev);
}
static unsigned long tegra_dfll_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct platform_device *pdev = tdc->pdev;
/* XXX Validate the current state of the DFLL first */
return tegra_dfll_request_get(pdev);
}
static long tegra_dfll_clk_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct platform_device *pdev = tdc->pdev;
struct dfll_rate_req req;
int r;
unsigned long dvco_rate;
req.rate = rate;
r = calc_req_scale_mult(pdev, &req, &dvco_rate);
if (r)
return r;
return calc_dfll_request_rate(pdev, &req);
}
static int tegra_dfll_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_dfll_clk_hw *tdc = to_tegra_dfll_clk_hw(hw);
struct platform_device *pdev = tdc->pdev;
return tegra_dfll_request_rate(pdev, rate);
}
static const struct clk_ops tegra_dfll_clk_ops = {
.is_enabled = tegra_dfll_clk_is_enabled,
.prepare = tegra_dfll_clk_prepare,
.unprepare = tegra_dfll_clk_unprepare,
.enable = tegra_dfll_clk_enable,
.disable = tegra_dfll_clk_disable,
.recalc_rate = tegra_dfll_clk_recalc_rate,
.round_rate = tegra_dfll_clk_round_rate,
.set_rate = tegra_dfll_clk_set_rate,
};
static const char *const dfll_clk_parents[] = { "dfll_soc" };
static const struct clk_init_data dfll_clk_init_data = {
.name = DFLL_OUTPUT_CLOCK_NAME,
.ops = &tegra_dfll_clk_ops,
.parent_names = (const char **)dfll_clk_parents,
.num_parents = ARRAY_SIZE(dfll_clk_parents),
};
/**
* register_dfll_clk - register the DFLL output clock with the clock framework
* @pdev: DFLL instance
*
* Register the DFLL's output clock (i.e., the clock source that the
* DVCO generates) with the Linux clock framework. Returns 0
* upon success or -EINVAL upon failure.
*/
static int register_dfll_clk(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int ret;
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, DFLL_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;
}
/*
* Debugfs interface
*/
#ifdef CONFIG_DEBUG_FS
static int enable_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
*val = (td->mode == TEGRA_DFLL_CLOSED_LOOP ||
td->mode == TEGRA_DFLL_OPEN_LOOP);
return 0;
}
static int enable_set(void *data, u64 val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
if (val)
clk_prepare_enable(td->dfll_clk);
else
clk_disable_unprepare(td->dfll_clk);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(enable_fops, enable_get, enable_set, "%llu\n");
static int lock_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
*val = td->mode == TEGRA_DFLL_CLOSED_LOOP;
return 0;
}
static int lock_set(void *data, u64 val)
{
struct platform_device *pdev = data;
int r;
if (val)
r = tegra_dfll_lock(pdev);
else
r = tegra_dfll_unlock(pdev);
if (r)
dev_err(&pdev->dev, "lock_set %llu returned %d\n", val, r);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(lock_fops, lock_get, lock_set, "%llu\n");
static int monitor_get(void *data, u64 *val)
{
u32 v, s;
unsigned long flags;
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
clk_enable(td->soc_clk);
spin_lock_irqsave(&td->lock, flags);
v = dfll_readl(td, DFLL_MONITOR_DATA) & DFLL_MONITOR_DATA_VAL_MASK;
if (dfll_readl(td, DFLL_MONITOR_CTRL) == DFLL_MONITOR_CTRL_FREQ) {
v = GET_MONITORED_RATE(v, td->ref_rate);
s = dfll_readl(td, DFLL_FREQ_REQ);
s = (s & DFLL_FREQ_REQ_SCALE_MASK) >> DFLL_FREQ_REQ_SCALE_SHIFT;
*val = (u64)v * (s + 1) / 256;
} else {
*val = v;
}
spin_unlock_irqrestore(&td->lock, flags);
clk_disable(td->soc_clk);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(monitor_fops, monitor_get, NULL, "%llu\n");
static int target_rate_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
*val = tegra_dfll_request_get(pdev);
return 0;
}
static int target_rate_set(void *data, u64 val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
return clk_set_rate(td->dfll_clk, val);
}
DEFINE_SIMPLE_ATTRIBUTE(target_rate_fops, target_rate_get, target_rate_set,
"%llu\n");
static int vmax_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
*val = td->out_map[td->lut_max]->reg_uv / 1000;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(vmax_fops, vmax_get, NULL, "%llu\n");
static int vmin_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
*val = td->out_map[td->lut_min]->reg_uv / 1000;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(vmin_fops, vmin_get, NULL, "%llu\n");
static int tune_high_mv_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
*val = td->tune_high_min_mv;
return 0;
}
static int tune_high_mv_set(void *data, u64 val)
{
unsigned long flags;
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int r, ret;
spin_lock_irqsave(&td->lock, flags);
td->tune_high_min_mv = val;
r = dfll_init_output_thresholds(pdev);
if (r) {
dev_err(&pdev->dev, "could not set output thresholds\n");
ret = -EINVAL;
goto thms_err;
}
if (td->mode == TEGRA_DFLL_CLOSED_LOOP)
reprogram_last_request(pdev);
ret = 0;
thms_err:
spin_unlock_irqrestore(&td->lock, flags);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(tune_high_mv_fops, tune_high_mv_get, tune_high_mv_set,
"%llu\n");
static int fmin_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
*val = td->dvco_rate_min;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(dvco_rate_min_fops, fmin_get, NULL, "%llu\n");
static int calibr_delay_get(void *data, u64 *val)
{
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
*val = jiffies_to_msecs(td->calibration_delay);
return 0;
}
static int calibr_delay_set(void *data, u64 val)
{
unsigned long flags;
struct platform_device *pdev = data;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
spin_lock_irqsave(&td->lock, flags);
td->calibration_delay = msecs_to_jiffies(val);
spin_unlock_irqrestore(&td->lock, flags);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(calibr_delay_fops, calibr_delay_get, calibr_delay_set,
"%llu\n");
static int cl_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);
clk_enable(td->soc_clk);
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);
clk_disable(td->soc_clk);
return 0;
}
static int cl_register_open(struct inode *inode, struct file *file)
{
return single_open(file, cl_register_show, inode->i_private);
}
static ssize_t cl_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;
/*
* terminate buffer and trim - white spaces may be appended at
* the end when invoked from shell command line
*/
buf[count] = '\0';
strim(buf);
if (sscanf(buf, "[0x%x] = 0x%x", &offs, &val) != 2)
return -1;
clk_enable(td->soc_clk);
dfll_writel(td, val, offs & (~0x3));
clk_disable(td->soc_clk);
return count;
}
static const struct file_operations cl_register_fops = {
.open = cl_register_open,
.read = seq_read,
.write = cl_register_write,
.llseek = seq_lseek,
.release = single_release,
};
static int tegra_dfll_debug_init(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int ret;
if (!td || (td->mode == TEGRA_DFLL_UNINITIALIZED))
return 0;
td->dent = debugfs_create_dir(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("lock", S_IRUGO | S_IWUSR,
td->dent, pdev, &lock_fops))
goto err_out;
if (!debugfs_create_file("monitor", S_IRUGO,
td->dent, pdev, &monitor_fops))
goto err_out;
if (!debugfs_create_file("target_rate", S_IRUGO,
td->dent, pdev, &target_rate_fops))
goto err_out;
if (!debugfs_create_file("vmax_mv", S_IRUGO,
td->dent, pdev, &vmax_fops))
goto err_out;
if (!debugfs_create_file("vmin_mv", S_IRUGO,
td->dent, pdev, &vmin_fops))
goto err_out;
if (!debugfs_create_file("tune_high_mv", S_IRUGO, td->dent, pdev,
&tune_high_mv_fops))
goto err_out;
if (!debugfs_create_file("dvco_min", S_IRUGO, td->dent, pdev,
&dvco_rate_min_fops))
goto err_out;
if (!debugfs_create_file("calibr_delay", S_IRUGO, td->dent, pdev,
&calibr_delay_fops))
goto err_out;
if (!debugfs_create_file("registers", S_IRUGO | S_IWUSR,
td->dent, pdev, &cl_register_fops))
goto err_out;
return 0;
err_out:
debugfs_remove_recursive(td->dent);
return ret;
}
#endif /* CONFIG_DEBUG_FS */
/*
* Interface for the thermal reaction driver and thermal zone
*/
/**
* tegra124_dfll_update_thermal_index - tell the DFLL how hot it is
* @pdev: DFLL instance
* @type: type of thermal floor or cap
* @new_idx: current DFLL temperature index (into therm_floors)
*
* Update the DFLL driver's sense of what temperature the DFLL is
* running at. @new_idx is an index into td->therm_floors - provided
* earlier to the thermal cooling driver. Intended to be called by
* the function supplied to the struct
* thermal_cooling_device_ops.set_cur_state function pointer. Returns
* 0 upon success or -ERANGE if @new_idx is out of range.
*/
int tegra124_dfll_update_thermal_index(struct platform_device *pdev,
enum tegra_dfll_therm_type type,
unsigned long new_idx)
{
unsigned long flags;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
if (type == TEGRA_DFLL_THERM_FLOOR) {
if (new_idx > td->therm_floors_num)
return -ERANGE;
spin_lock_irqsave(&td->lock, flags);
td->therm_floors_idx = new_idx;
set_dvco_rate_min(pdev);
if (td->mode == TEGRA_DFLL_CLOSED_LOOP)
reprogram_last_request(pdev);
spin_unlock_irqrestore(&td->lock, flags);
} else if (type == TEGRA_DFLL_THERM_CAP) {
if (new_idx > td->therm_caps_num)
return -ERANGE;
spin_lock_irqsave(&td->lock, flags);
td->therm_caps_idx = new_idx;
if (td->mode == TEGRA_DFLL_CLOSED_LOOP)
reprogram_last_request(pdev);
spin_unlock_irqrestore(&td->lock, flags);
}
return 0;
}
EXPORT_SYMBOL(tegra124_dfll_update_thermal_index);
/**
* tegra124_dfll_get_thermal_index - return the DFLL's current thermal state
* @pdev: DFLL instance
* @type: type of thermal floor or cap
*
* Return the DFLL driver's copy of the DFLL's current temperature
* index, set by tegra124_dfll_update_thermal_index(). Intended to be
* called by the function supplied to the struct
* thermal_cooling_device_ops.get_cur_state function pointer.
*/
int tegra124_dfll_get_thermal_index(struct platform_device *pdev,
enum tegra_dfll_therm_type type)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
if (type == TEGRA_DFLL_THERM_FLOOR)
return td->therm_floors_idx;
else if (type == TEGRA_DFLL_THERM_CAP)
return td->therm_caps_idx;
else
return -EINVAL;
}
EXPORT_SYMBOL(tegra124_dfll_get_thermal_index);
/**
* tegra124_dfll_count_therm_states - return the number of thermal states
* @pdev: DFLL instance
* @type: type of thermal floor or cap
*
* Return the number of thermal states passed into the DFLL driver
* from the DT data. Intended to be called by the function supplied
* to the struct thermal_cooling_device_ops.get_max_state function
* pointer, and by the integration code that binds a thermal zone to
* the DFLL thermal reaction driver.
*/
int tegra124_dfll_count_therm_states(struct platform_device *pdev,
enum tegra_dfll_therm_type type)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
if (type == TEGRA_DFLL_THERM_FLOOR)
return td->therm_floors_num;
else if (type == TEGRA_DFLL_THERM_CAP)
return td->therm_caps_num;
else
return -EINVAL;
}
EXPORT_SYMBOL(tegra124_dfll_count_therm_states);
/**
* tegra124_dfll_get_therm_state_temp - return the state temp at @index
* @pdev: DFLL instance
* @type: type of thermal floor or cap
* @index: index into the therm_floors or therm_caps array (zero-based)
*
* Return the temperature corresponding to @index from the therm_floors or
* therm_caps array. Intended to be called by integration code
* binding a thermal zone to the DFLL thermal reaction driver.
* Returns -ERANGE if @index would result in an access off the end of
* the array, or returns the temperature corresponding to @index in
* degrees Celsius.
*/
int tegra124_dfll_get_therm_state_temp(struct platform_device *pdev,
enum tegra_dfll_therm_type type,
unsigned long index)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
if (type == TEGRA_DFLL_THERM_FLOOR) {
if (index >= td->therm_floors_num)
return -ERANGE;
return td->therm_floors[index].temp;
} else if (type == TEGRA_DFLL_THERM_CAP) {
if (index >= td->therm_caps_num)
return -ERANGE;
return td->therm_caps[index].temp;
} else {
return -EINVAL;
}
}
EXPORT_SYMBOL(tegra124_dfll_get_therm_state_temp);
/**
* tegra124_dfll_attach_thermal - attach the "cooling device" @cdev to @pdev
* @pdev: DFLL instance
* @type: type of thermal floor or cap
* @cdev: DFLL "cooling device" instance
*
* Attach a thermal reaction "cooling device" to the DFLL instance
* @pdev. As long as a thermal reaction driver is attached, the DFLL
* driver can't be unbound from the DFLL device. Returns -EINVAL if
* the DFLL instance hasn't been initialized yet, -EBUSY if a thermal
* driver is already associated with @pdev, or 0 upon success.
*/
int tegra124_dfll_attach_thermal(struct platform_device *pdev,
enum tegra_dfll_therm_type type,
struct thermal_cooling_device *cdev)
{
unsigned long flags;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int ret = -EINVAL;
if (!td)
return ret;
spin_lock_irqsave(&td->lock, flags);
if (td->mode == TEGRA_DFLL_UNINITIALIZED) {
dev_err(&pdev->dev, "DFLL not yet initialized\n");
ret = -EINVAL;
goto tdat_out;
}
if (type == TEGRA_DFLL_THERM_FLOOR) {
if (td->cdev_floor) {
dev_err(&pdev->dev,
"DFLL floor already bound to thermal driver\n");
ret = -EBUSY;
goto tdat_out;
}
td->cdev_floor = cdev;
ret = 0;
} else if (type == TEGRA_DFLL_THERM_CAP) {
if (td->cdev_cap) {
dev_err(&pdev->dev,
"DFLL cap already bound to thermal driver\n");
ret = -EBUSY;
goto tdat_out;
}
td->cdev_cap = cdev;
ret = 0;
} else {
ret = -EINVAL;
}
tdat_out:
spin_unlock_irqrestore(&td->lock, flags);
return ret;
}
EXPORT_SYMBOL(tegra124_dfll_attach_thermal);
/**
* tegra124_dfll_detach_thermal - detach the "cooling device" @cdev from @pdev
* @pdev: DFLL instance
* @type: type of thermal floor or cap
* @cdev: DFLL "cooling device" instance
*
* Detach a thermal reaction "cooling device" from the DFLL instance
* @pdev. Returns -EINVAL if @cdev isn't currently associated with
* the DFLL instance, or 0 upon success.
*/
int tegra124_dfll_detach_thermal(struct platform_device *pdev,
enum tegra_dfll_therm_type type,
struct thermal_cooling_device *cdev)
{
unsigned long flags;
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int ret = -EINVAL;
spin_lock_irqsave(&td->lock, flags);
if (type == TEGRA_DFLL_THERM_FLOOR) {
if (td->cdev_floor != cdev) {
dev_err(&pdev->dev, "floor cooling device mismatch\n");
ret = -EINVAL;
goto tddt_out;
}
td->cdev_floor = NULL;
td->therm_floors_idx = 0;
ret = 0;
} else if (type == TEGRA_DFLL_THERM_CAP) {
if (td->cdev_cap != cdev) {
dev_err(&pdev->dev, "cap cooling device mismatch\n");
ret = -EINVAL;
goto tddt_out;
}
td->cdev_cap = NULL;
td->therm_caps_idx = 0;
ret = 0;
} else {
ret = -EINVAL;
}
tddt_out:
spin_unlock_irqrestore(&td->lock, flags);
return ret;
}
EXPORT_SYMBOL(tegra124_dfll_detach_thermal);
/*
* Interface to lock and unlock DFLL loop
*/
/**
* tegra124_dfll_lock_loop - enable closed loop
*
* The wrapper of tegra_dfll_lock(). Return -EPERM if the static DFLL
* platform device instance does not get initialized.
*/
int tegra124_dfll_lock_loop(void)
{
if (!fcpu_dfll_pdev) {
WARN(1, "Tegra124 DFLL is not initialized\n");
return -EPERM;
}
return tegra_dfll_lock(fcpu_dfll_pdev);
}
EXPORT_SYMBOL(tegra124_dfll_lock_loop);
/**
* tegra124_dfll_unlock_loop - disable closed loop
*
* The wrapper of tegra_dfll_lock(). Return -EPERM if the static DFLL
* platform device instance does not get initialized.
*/
int tegra124_dfll_unlock_loop(void)
{
if (!fcpu_dfll_pdev) {
WARN(1, "Tegra124 DFLL is not initialized\n");
return -EPERM;
}
return tegra_dfll_unlock(fcpu_dfll_pdev);
}
EXPORT_SYMBOL(tegra124_dfll_unlock_loop);
/**
* tegra124_dfll_get_fv_table - get freq/volt table for cpug
*
* @num_freqs: number of frequencies
* @freqs: the array of frequencies
* @millivolts: the array of voltages
*/
int tegra124_dfll_get_fv_table(int *num_freqs, unsigned long **freqs,
int **millivolts)
{
struct tegra_dfll *td;
if (!fcpu_dfll_pdev)
return -EPERM;
if (!num_freqs || !freqs || !millivolts)
return -EINVAL;
td = dev_get_drvdata(&fcpu_dfll_pdev->dev);
*num_freqs = td->dvfs_info->num_freqs;
*freqs = td->dvfs_info->freqs;
*millivolts = td->dvfs_info->cpu_dfll_millivolts;
return 0;
}
EXPORT_SYMBOL(tegra124_dfll_get_fv_table);
/*
* DFLL initialization
*/
/**
* tegra_dfll_init_clks - clk_get() the DFLL source clocks
* @pdev: DFLL instance
*
* 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 tegra_dfll_init_clks(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int ret;
td->ref_clk = devm_clk_get(&pdev->dev, "ref");
if (IS_ERR(td->ref_clk)) {
dev_err(&pdev->dev, "missing ref clock\n");
ret = -ENODEV;
goto tdic_err;
}
td->soc_clk = devm_clk_get(&pdev->dev, "soc");
if (IS_ERR(td->soc_clk)) {
dev_err(&pdev->dev, "missing soc clock\n");
ret = -ENODEV;
goto tdic_err;
}
td->i2c_clk = devm_clk_get(&pdev->dev, "i2c");
if (IS_ERR(td->i2c_clk)) {
dev_err(&pdev->dev, "missing i2c clock\n");
ret = -ENODEV;
goto tdic_err;
}
return 0;
tdic_err:
return ret;
}
/**
* tegra_dfll_regulator_probe_voltages - build vdd_map[] from the regulator
* @pdev: DFLL instance
*
* Build the vdd_map from regulator framework and DFLL DT data.
* Returns 0 upon success, or -ENOSPC on a memory allocation failure.
*/
static int tegra_dfll_regulator_probe_voltages(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int c, i, j, vdd_uv;
struct tegra_dfll_voltage_reg_map *vdd_map;
c = regulator_count_voltages(td->vdd);
if (c < 0)
return c;
vdd_map = devm_kzalloc(&pdev->dev,
sizeof(struct tegra_dfll_voltage_reg_map) * c,
GFP_KERNEL);
if (!vdd_map)
return -ENOMEM;
j = 0;
for (i = 0; i < c; i++) {
vdd_uv = regulator_list_voltage(td->vdd, i);
if (vdd_uv <= 0)
continue;
if (vdd_uv < td->dfll_min_microvolt)
continue;
if (vdd_uv > td->dfll_max_microvolt)
break;
vdd_map[j].reg_value = i;
vdd_map[j].reg_uv = vdd_uv;
j++;
}
td->vdd_map_size = j;
td->vdd_map = vdd_map;
return 0;
}
/*
* DT data fetch
*/
/**
* parse_of_dfll_pmic_integration - find and extract the DT DFLL-PMIC data
* @pdev: DFLL instance
* @dn: DT node of the DFLL instance data
*
* Read the DFLL PMIC integration data from the DT node with the
* "nvidia,tegra124-dfll,pmic-integration" property. This data
* includes the PMIC I2C address, the I2C bus speed, the I2C voltage
* register address. Returns 0 upon success or -EINVAL upon error.
*/
static int parse_of_dfll_pmic_integration(struct platform_device *pdev,
struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
const __be32 *prop;
struct device_node *p_dn;
prop = of_get_property(dn, "pmic-integration", NULL);
if (!prop) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"pmic-integration");
return -EINVAL;
}
p_dn = of_find_node_by_phandle(be32_to_cpup(prop));
if (!p_dn) {
dev_err(&pdev->dev, "missing DFLL PMIC integration DT data\n");
return -EINVAL;
}
if (of_property_read_u32(p_dn, "pmic-i2c-address",
&td->pmic_i2c_addr)) {
dev_err(&pdev->dev, "missing PMIC I2C address\n");
return -EINVAL;
}
if (of_property_read_u32(p_dn, "pmic-i2c-voltage-register",
&td->pmic_i2c_voltage_reg)) {
dev_err(&pdev->dev,
"missing PMIC I2C voltage register address\n");
return -EINVAL;
}
if (of_property_read_u32(p_dn, "dfll-min-microvolt",
&td->dfll_min_microvolt)) {
dev_err(&pdev->dev,
"missing DFLL regulator minimum voltage\n");
return -EINVAL;
}
if (of_property_read_u32(p_dn, "dfll-max-microvolt",
&td->dfll_max_microvolt)) {
dev_err(&pdev->dev,
"missing DFLL regulator maximum voltage\n");
return -EINVAL;
}
of_property_read_u32(p_dn, "i2c-hs-rate", &td->pmic_i2c_hs_rate);
if ((td->pmic_i2c_hs_rate > 0) &&
of_property_read_u32(p_dn, "i2c-hs-master-code",
&td->pmic_i2c_hs_master_code)) {
dev_err(&pdev->dev, "missing I2C HS master code\n");
return -EINVAL;
}
td->pmic_i2c_ten_bit_addrs = !!of_get_property(p_dn,
"i2c-10-bit-addresses",
NULL);
if (of_property_read_u32(p_dn, "i2c-fs-rate", &td->pmic_i2c_fs_rate)) {
dev_err(&pdev->dev, "missing I2C FS bus rate\n");
return -EINVAL;
}
return 0;
}
/**
* parse_of_dfll_board_data - find and extract the DFLL per-board parameters
* @pdev: DFLL instance
* @dn: DT node of the DFLL instance data
*
* Read the DFLL per-board data from the DT node with the
* "nvidia,tegra124-dfll,board-data" compatible property. This data
* is the result of the DFLL characterization process for a particular
* board design. It includes loop and voltage droop avoidance
* parameters. It does not include PMIC integration; see
* parse_of_dfll_pmic_integration() for that. Returns 0 upon success
* or -EINVAL upon error.
*/
static int parse_of_dfll_board_data(struct platform_device *pdev,
struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct device_node *b_dn;
int fixed_forcing, auto_forcing, no_forcing;
int i = 0, ret = 0;
const __be32 *prop;
u32 dtp;
prop = of_get_property(dn, "board-params", NULL);
if (!prop) {
dev_err(&pdev->dev, "missing %s in DT data\n", "board-params");
return -EINVAL;
}
b_dn = of_find_node_by_phandle(be32_to_cpup(prop));
if (!b_dn) {
dev_err(&pdev->dev, "missing DFLL board DT data\n");
return -EINVAL;
}
if (of_property_read_u32(b_dn, "sample-rate", &td->sample_rate)) {
dev_err(&pdev->dev, "missing %s in DT data\n", "sample-rate");
return -EINVAL;
}
if (of_property_read_u32(b_dn, "cf", &td->cf)) {
dev_err(&pdev->dev, "missing %s in DT data\n", "cf");
return -EINVAL;
}
if (of_property_read_u32(b_dn, "ci", &td->ci)) {
dev_err(&pdev->dev, "missing %s in DT data\n", "ci");
return -EINVAL;
}
if (of_property_read_u32(b_dn, "cg", &td->cg)) {
dev_err(&pdev->dev, "missing %s in DT data\n", "cg");
return -EINVAL;
}
dtp = !!of_get_property(b_dn, "cg-scale", NULL);
if (dtp)
td->cg_scale = 1;
if (of_property_read_u32(b_dn, "droop-cut-value",
&td->droop_cut_value)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"droop-cut-value");
return -EINVAL;
}
if (of_property_read_u32(b_dn, "droop-restore-ramp",
&td->droop_restore_ramp)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"droop-restore-ramp");
return -EINVAL;
}
if (of_property_read_u32(b_dn, "scale-out-ramp", &td->scale_out_ramp)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"scale-out-ramp");
return -EINVAL;
}
fixed_forcing = !!of_get_property(b_dn, "fixed-output-forcing", NULL);
auto_forcing = !!of_get_property(b_dn, "auto-output-forcing", NULL);
no_forcing = !!of_get_property(b_dn, "no-output-forcing", NULL);
if (fixed_forcing) {
td->force_mode = TEGRA_DFLL_FORCE_FIXED;
i++;
}
if (auto_forcing) {
td->force_mode = TEGRA_DFLL_FORCE_AUTO;
i++;
}
if (no_forcing) {
td->force_mode = TEGRA_DFLL_FORCE_NONE;
i++;
}
if (i != 1) {
dev_err(&pdev->dev,
"must specify one and only one forcing param in DT\n");
return -EINVAL;
}
return ret;
}
/**
* parse_of_dfll_tuning - read the DFLL tuning data from DT
* @pdev: DFLL instance
* @dn: DT node of the DFLL instance data
*
* Read the DFLL tuning data from DT. Returns 0 upon success, -EINVAL
* if required data fields are missing, or -ENOENT if no DFLL tuning
* node could be found that matches the current chip's Speedo ID and
* process id.
*/
static int parse_of_dfll_tuning(struct platform_device *pdev,
struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct device_node *t_dn, *tc_dn;
const __be32 *prop;
u32 speedo_id, process_id;
prop = of_get_property(dn, "tuning", NULL);
if (!prop) {
dev_err(&pdev->dev, "missing %s in DT data\n", "tuning");
return -EINVAL;
}
t_dn = of_find_node_by_phandle(be32_to_cpup(prop));
if (!t_dn) {
dev_err(&pdev->dev, "missing DFLL tuning DT data\n");
return -EINVAL;
}
/* Read the loop params */
for_each_child_of_node(t_dn, tc_dn) {
if (of_property_read_u32(tc_dn, "speedo-id", &speedo_id)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"speedo-id");
return -EINVAL;
}
if (speedo_id != td->speedo_id)
continue;
if (!of_property_read_u32(tc_dn, "process-id", &process_id))
if (process_id != td->process_id)
continue;
if (of_property_read_u32(tc_dn, "tune0-low-voltage-range",
&td->tune0_low_voltage_range)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"tune0-low-voltage-range");
return -EINVAL;
}
if (of_property_read_u32(tc_dn, "tune0-high-voltage-range",
&td->tune0_high_voltage_range)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"tune0-high-voltage-range");
return -EINVAL;
}
if (of_property_read_u32(tc_dn, "tune1", &td->tune1)) {
dev_err(&pdev->dev, "missing %s in DT data\n", "tune1");
return -EINVAL;
}
if (of_property_read_u32(tc_dn, "droop-rate-min",
&td->droop_rate_min)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"droop-rate-min");
return -EINVAL;
}
if (of_property_read_u32(tc_dn, "min-millivolts",
&td->min_millivolts)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"min-millivolts");
return -EINVAL;
}
of_property_read_u32(tc_dn, "tune-high-min-millivolts",
&td->tune_high_min_mv);
return 0;
}
return -ENOENT;
}
/**
* parse_of_cvbs - parse the per-frequency voltage curve data
* @pdev: DFLL instance
* @dn: struct device_node * to the "cvb" DT node
*
* Read the CVB per-frequency data from DT. Other code walks this
* table and builds a "cooked" table, used during runtime. Returns 0
* upon success or -EINVAL upon error.
*/
static int parse_of_cvbs(struct platform_device *pdev, struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct tegra_dfll_cvb *tdc;
struct property *prop;
const __be32 *p;
u32 u;
int i, j, l, rem;
prop = of_find_property(dn, "cvb-voltage-curves", &l);
if (!prop || (l == 0)) {
dev_err(&pdev->dev, "missing %s in DT data\n", "cvb");
return -EINVAL;
}
l /= sizeof(__be32);
rem = l % CVB_ROW_WIDTH;
if (rem > 0) {
dev_err(&pdev->dev, "CVB data format error\n");
return -EINVAL;
}
l /= CVB_ROW_WIDTH;
tdc = devm_kzalloc(&pdev->dev, l * sizeof(struct tegra_dfll_cvb),
GFP_KERNEL);
if (!tdc)
goto poct_err;
i = 0;
of_property_for_each_u32(dn, "cvb-voltage-curves", prop, p, u) {
j = i / CVB_ROW_WIDTH;
switch (i % CVB_ROW_WIDTH) {
case 0:
tdc[j].freq = u;
break;
case 1:
tdc[j].c0 = u;
break;
case 2:
tdc[j].c1 = u;
break;
case 3:
tdc[j].c2 = u;
break;
}
i++;
}
td->dvfs_info->cvb_table = tdc;
td->dvfs_info->cvb_table_len = i / CVB_ROW_WIDTH;
return 0;
poct_err:
return -EINVAL;
}
/**
* parse_of_cvb_table_meta - read the CVB table metadata from DT
* @pdev: DFLL instance
* @lpc_dn: 'characterization' DT node
*
* Read the CVB table metadata from the DeviceTree data. Returns 0
* upon success, or -EINVAL upon failure.
*/
static int parse_of_cvb_table_meta(struct platform_device *pdev,
struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
if (of_property_read_u32(dn, "cvb-max-millivolts",
&td->cvb_max_millivolts)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"cvb-max-millivolts");
return -EINVAL;
}
if (of_property_read_u32(dn, "cvb-speedo-scale",
&td->cvb_speedo_scale)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"cvb-speedo-scale");
return -EINVAL;
}
if (of_property_read_u32(dn, "cvb-voltage-scale",
&td->cvb_voltage_scale)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"cvb-voltage-scale");
return -EINVAL;
}
return 0;
}
/**
* parse_of_therm_caps - parse the thermal voltage caps
* @pdev: DFLL instance
* @dn: struct device_node * of the "characterization" DT node
*
* Read the array of u32s from the 'therm-caps' property, under the
* DT device node @dn. The first of the two values represents the
* trip point temperature in degrees Celsius. The second value
* represents the maximum voltage cap for that temperature, and, if
* there are no lower temperatures specified, any temperatures below
* it. Returns 0 if the 'therm-caps' property doesn't exist (it's
* optional) or if the data was read successfully, or -EINVAL if
* something is wrong with the DT data.
*/
static int parse_of_therm_caps(struct platform_device *pdev,
struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct property *prop;
const __be32 *p;
u32 u;
int i, j;
i = 0;
of_property_for_each_u32(dn, "therm-caps", prop, p, u) {
j = i / THERM_CAPS_ROW_WIDTH;
if (j >= MAX_THERMAL_CAPS)
break;
if (!(i % THERM_CAPS_ROW_WIDTH)) {
/* XXX Test u32 -> u8 overflow */
td->therm_caps[j].temp = u;
} else {
td->therm_caps[j].mv = u;
}
i++;
}
if (i % THERM_CAPS_ROW_WIDTH) {
dev_err(&pdev->dev, "therm-caps data format error\n");
return -EINVAL;
}
td->therm_caps_num = i / THERM_CAPS_ROW_WIDTH;
return 0;
}
/**
* parse_of_therm_floors - parse the thermal voltage floors
* @pdev: DFLL instance
* @dn: struct device_node * of the "characterization" DT node
*
* Read the array of u32s from the 'therm-floors' property, under the
* DT device node @dn. The first of the two values represents the
* trip point temperature in degrees Celsius. The second value
* represents the minimum voltage floor for that temperature, and, if
* there are no lower temperatures specified, any temperatures below
* it. Returns 0 if the 'therm-floors' property doesn't exist (it's
* optional) or if the data was read successfully, or -EINVAL if
* something is wrong with the DT data.
*/
static int parse_of_therm_floors(struct platform_device *pdev,
struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct property *prop;
const __be32 *p;
u32 u;
int i, j;
i = 0;
of_property_for_each_u32(dn, "therm-floors", prop, p, u) {
j = i / THERM_FLOORS_ROW_WIDTH;
if (j >= MAX_THERMAL_FLOORS)
break;
if (!(i % THERM_FLOORS_ROW_WIDTH)) {
/* XXX Test u32 -> u8 overflow */
td->therm_floors[j].temp = u;
} else {
td->therm_floors[j].mv = u;
}
i++;
}
if (i % THERM_FLOORS_ROW_WIDTH) {
dev_err(&pdev->dev, "therm-floors data format error\n");
return -EINVAL;
}
td->therm_floors_num = i / THERM_FLOORS_ROW_WIDTH;
return 0;
}
/**
* parse_of_cvb_table - read CVB table data from DeviceTree
* @pdev: DFLL instance
* @dn: DT node of the DFLL instance data
*
* Reads the CVB table data and CVB metadata that corresponds to the
* current chip's Speedo and process ID. Returns 0 upon success,
* -EINVAL upon error, or -ENOENT if no CVB table node could be found
* that matches the current chip's Speedo ID and process id.
*/
static int parse_of_cvb_table(struct platform_device *pdev,
struct device_node *dn)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
struct device_node *d_dn, *dc_dn;
const __be32 *prop;
u32 speedo_id, process_id;
int r;
prop = of_get_property(dn, "cvb-table", NULL);
if (!prop) {
dev_err(&pdev->dev, "missing %s in DT data\n", "cvb-table");
return -EINVAL;
}
d_dn = of_find_node_by_phandle(be32_to_cpup(prop));
if (!d_dn) {
dev_err(&pdev->dev, "missing DFLL cvb table DT data\n");
return -EINVAL;
}
for_each_child_of_node(d_dn, dc_dn) {
if (of_property_read_u32(dc_dn, "speedo-id", &speedo_id)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"speedo-id");
return -EINVAL;
}
if (speedo_id != td->speedo_id)
continue;
if (!of_property_read_u32(dc_dn, "process-id", &process_id))
if (process_id != td->process_id)
continue;
if (parse_of_therm_caps(pdev, dc_dn)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"therm-caps");
return -EINVAL;
}
if (parse_of_therm_floors(pdev, dc_dn)) {
dev_err(&pdev->dev, "missing %s in DT data\n",
"therm-floors");
return -EINVAL;
}
r = parse_of_cvb_table_meta(pdev, dc_dn);
if (r) {
dev_err(&pdev->dev, "couldn't parse CVB table meta\n");
return r;
}
r = parse_of_cvbs(pdev, dc_dn);
if (r) {
dev_err(&pdev->dev, "couldn't parse CVB table\n");
return r;
}
return 0;
}
return -ENOENT;
}
/**
* tegra_dfll_init_pmic_data - initialize PMIC regulator data
* @pdev: DFLL instance
*
* Read the PMIC integration data, including regulator data, from DT
* and the the regulator framework. Build the voltage map from
* regulator data. Returns 0 upon success or -EINVAL upon error.
*/
static int __must_check tegra_dfll_init_pmic_data(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
int r;
td->vdd = devm_regulator_get(&pdev->dev, "vdd");
if (IS_ERR(td->vdd)) {
dev_err(&pdev->dev, "couldn't locate regulator\n");
return -EINVAL;
}
td->vdd_step = regulator_get_linear_step(td->vdd);
if (!td->vdd_step) {
dev_err(&pdev->dev, "only linear map regulators supported\n");
goto tdipd_err;
}
r = parse_of_dfll_pmic_integration(pdev, pdev->dev.of_node);
if (r) {
dev_err(&pdev->dev, "DFLL PMIC integration parse error\n");
goto tdipd_err;
}
r = tegra_dfll_regulator_probe_voltages(pdev);
if (r) {
dev_err(&pdev->dev, "couldn't probe regulator voltages\n");
goto tdipd_err;
}
return 0;
tdipd_err:
return -EINVAL;
}
/*
* platform_device integration
*/
/**
* tegra_dfll_probe - probe the instance of the DFLL IP block
* @pdev: DFLL instance
*
* Called when a DFLL device is bound to this driver by the driver
* core. Registers the DFLL output clock with the clock framework.
* Returns 0 upon success, or -ENOMEM if memory couldn't be allocated,
* -EINVAL if various calls fail, or can pass along the error returned
* by several other functions.
*/
static int tegra_dfll_probe(struct platform_device *pdev)
{
struct device_node *dn = pdev->dev.of_node;
struct resource *mem;
struct tegra_dfll *td;
struct tegra_dfll_dvfs_info *dvfs_info;
int ret = -EINVAL;
int r;
td = devm_kzalloc(&pdev->dev, sizeof(*td), GFP_KERNEL);
if (!td)
return -ENOMEM;
dvfs_info = devm_kzalloc(&pdev->dev, sizeof(*dvfs_info), GFP_KERNEL);
if (!dvfs_info)
return -ENOMEM;
td->dvfs_info = dvfs_info;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem) {
dev_err(&pdev->dev, "missing register MMIO resource\n");
ret = -EINVAL;
goto tdp_err;
}
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 tdp_err;
}
td->speedo_id = tegra_get_cpu_speedo_id();
td->process_id = tegra_get_cpu_process_id();
td->speedo_value = tegra_get_cpu_speedo_value();
platform_set_drvdata(pdev, td);
r = tegra_dfll_init_clks(pdev);
if (r) {
dev_err(&pdev->dev, "DFLL clock init error\n");
ret = r;
goto tdp_err;
}
/* Set some parameters from the IP block's DT data */
r = parse_of_cvb_table(pdev, dn);
if (r) {
dev_err(&pdev->dev, "DFLL CVB data parse error\n");
ret = r;
goto tdp_err;
}
r = parse_of_dfll_tuning(pdev, dn);
if (r) {
dev_err(&pdev->dev, "DFLL tuning data parse error\n");
ret = r;
goto tdp_err;
}
r = parse_of_dfll_board_data(pdev, dn);
if (r) {
dev_err(&pdev->dev, "DFLL board data parse error\n");
ret = -EINVAL;
goto tdp_err;
}
r = tegra_dfll_init_pmic_data(pdev);
if (r) {
dev_err(&pdev->dev, "DFLL PMIC data parse error\n");
ret = r;
goto tdp_err;
}
/* Enable the clocks and set the device up */
ret = dfll_init(pdev);
if (ret)
goto tdp_err;
fcpu_dfll_pdev = pdev;
ret = register_dfll_clk(pdev);
if (ret) {
dev_err(&pdev->dev, "DFLL clk registration failed\n");
goto tdp_err;
}
#ifdef CONFIG_DEBUG_FS
tegra_dfll_debug_init(pdev);
#endif
dev_info(&pdev->dev, "Tegra T124 DFLL clock driver initialized\n");
return 0;
tdp_err:
fcpu_dfll_pdev = NULL;
iounmap(td->base);
return ret;
}
/**
* tegra_dfll_remove - unbind the DFLL driver from the DFLL device
* @pdev: DFLL instance
*
* Releases the DFLL platform_driver from the DFLL platform_device
* @pdev. The DFLL must be in the DISABLED state before this can be
* successful. Returns 0 upon success, or -EBUSY if the DFLL is still
* generating a clock.
*/
static int tegra_dfll_remove(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 != TEGRA_DFLL_DISABLED) {
dev_err(&pdev->dev,
"must disable DFLL before removing driver\n");
return -EBUSY;
}
if (td->cdev_floor || td->cdev_cap) {
dev_err(&pdev->dev,
"must unload thermal driver before removing DFLL\n");
return -EBUSY;
}
clk_unregister(td->dfll_clk);
tegra124_clock_assert_dfll_dvco_reset();
regulator_put(td->vdd);
iounmap(td->base);
kfree(td);
fcpu_dfll_pdev = NULL;
return 0;
}
/*
* dfll controls clock/voltage to other devices, including CPU. Therefore,
* dfll driver pm suspend callback does not stop cl-dvfs operations. It is
* only used to enforce cold voltage limit, since SoC may cool down during
* suspend without waking up. The correct temperature zone after supend will
* be updated via dfll cooling device interface during resume of temperature
* sensor.
*/
int tegra124_dfll_suspend(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
unsigned long flags;
spin_lock_irqsave(&td->lock, flags);
td->therm_caps_idx = td->therm_caps_num;
td->therm_floors_idx = 0;
set_dvco_rate_min(pdev);
if (td->mode == TEGRA_DFLL_CLOSED_LOOP) {
set_cl_config(pdev, &td->last_req);
set_request(pdev, &td->last_req);
}
spin_unlock_irqrestore(&td->lock, flags);
if (td->mode == TEGRA_DFLL_CLOSED_LOOP) {
tegra_dfll_unlock(pdev);
td->resume_mode = TEGRA_DFLL_CLOSED_LOOP;
}
return 0;
}
/**
* tegra_dfll_resume - reprogram the DFLL after context-loss
* @pdev: DFLL instance
*
* Re-initialize and enable target device clock in open loop mode. Called
* directly from SoC clock resume syscore operation. Closed loop will be
* re-entered in platform syscore ops as well.
*/
void tegra124_dfll_resume(struct platform_device *pdev)
{
struct tegra_dfll *td = dev_get_drvdata(&pdev->dev);
enum tegra_dfll_ctrl_mode mode = td->mode;
struct dfll_rate_req req = td->last_req;
unsigned long flags;
int ret;
spin_lock_irqsave(&td->lock, flags);
tegra124_clock_deassert_dfll_dvco_reset();
dfll_enable_clocks(pdev);
/* Setup PMU interface, and configure controls in disabled mode */
dfll_init_out_if(pdev);
ret = dfll_init_cntrl_logic(pdev);
if (ret)
dev_err(&pdev->dev, "could not init the control logic\n");
dfll_disable_clocks(pdev);
/* Restore last request and mode */
td->last_req = req;
if (mode != TEGRA_DFLL_DISABLED) {
set_mode(pdev, TEGRA_DFLL_OPEN_LOOP);
WARN(mode > TEGRA_DFLL_OPEN_LOOP,
"DFLL was left locked in suspend\n");
}
spin_unlock_irqrestore(&td->lock, flags);
if (td->resume_mode == TEGRA_DFLL_CLOSED_LOOP) {
tegra_dfll_lock(pdev);
td->resume_mode = TEGRA_DFLL_UNINITIALIZED;
}
}
/* Match table for OF platform binding */
static struct of_device_id tegra_dfll_of_match[] = {
{ .compatible = "nvidia,tegra124-dfll", },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_dfll_of_match);
static struct platform_driver tegra_dfll_driver = {
.probe = tegra_dfll_probe,
.remove = tegra_dfll_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(tegra_dfll_of_match),
},
};
static int __init tegra_dfll_driver_init(void)
{
return platform_driver_register(&tegra_dfll_driver);
}
static void __exit tegra_dfll_driver_exit(void)
{
platform_driver_unregister(&tegra_dfll_driver);
}
fs_initcall(tegra_dfll_driver_init);
module_exit(tegra_dfll_driver_exit);
MODULE_DESCRIPTION("Tegra DFLL clock source driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRIVER_NAME);
MODULE_AUTHOR("Aleksandr Frid <afrid@nvidia.com>");
MODULE_AUTHOR("Paul Walmsley <pwalmsley@nvidia.com>");