/* * clk-t124-dfll.c - clock provider support for the Tegra124 DFLL clock source * * Copyright (C) 2012-2013 NVIDIA Corporation. All rights reserved. * * Aleksandr Frid * Paul Walmsley * * 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 . * * ... * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 #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 "); MODULE_AUTHOR("Paul Walmsley ");