// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 Samsung Electronics Co., Ltd. * Author: Lukasz Luba <[email protected]> */ #include <linux/clk.h> #include <linux/devfreq.h> #include <linux/devfreq-event.h> #include <linux/device.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/of.h> #include <linux/pm_opp.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include "../jedec_ddr.h" #include "../of_memory.h" static int irqmode; module_param(irqmode, int, 0644); MODULE_PARM_DESC(…) …; #define EXYNOS5_DREXI_TIMINGAREF … #define EXYNOS5_DREXI_TIMINGROW0 … #define EXYNOS5_DREXI_TIMINGDATA0 … #define EXYNOS5_DREXI_TIMINGPOWER0 … #define EXYNOS5_DREXI_TIMINGROW1 … #define EXYNOS5_DREXI_TIMINGDATA1 … #define EXYNOS5_DREXI_TIMINGPOWER1 … #define CDREX_PAUSE … #define CDREX_LPDDR3PHY_CON3 … #define CDREX_LPDDR3PHY_CLKM_SRC … #define EXYNOS5_TIMING_SET_SWI … #define USE_MX_MSPLL_TIMINGS … #define USE_BPLL_TIMINGS … #define EXYNOS5_AREF_NORMAL … #define DREX_PPCCLKCON … #define DREX_PEREV2CONFIG … #define DREX_PMNC_PPC … #define DREX_CNTENS_PPC … #define DREX_CNTENC_PPC … #define DREX_INTENS_PPC … #define DREX_INTENC_PPC … #define DREX_FLAG_PPC … #define DREX_PMCNT2_PPC … /* * A value for register DREX_PMNC_PPC which should be written to reset * the cycle counter CCNT (a reference wall clock). It sets zero to the * CCNT counter. */ #define CC_RESET … /* * A value for register DREX_PMNC_PPC which does the reset of all performance * counters to zero. */ #define PPC_COUNTER_RESET … /* * Enables all configured counters (including cycle counter). The value should * be written to the register DREX_PMNC_PPC. */ #define PPC_ENABLE … /* A value for register DREX_PPCCLKCON which enables performance events clock. * Must be written before first access to the performance counters register * set, otherwise it could crash. */ #define PEREV_CLK_EN … /* * Values which are used to enable counters, interrupts or configure flags of * the performance counters. They configure counter 2 and cycle counter. */ #define PERF_CNT2 … #define PERF_CCNT … /* * Performance event types which are used for setting the preferred event * to track in the counters. * There is a set of different types, the values are from range 0 to 0x6f. * These settings should be written to the configuration register which manages * the type of the event (register DREX_PEREV2CONFIG). */ #define READ_TRANSFER_CH0 … #define READ_TRANSFER_CH1 … #define PERF_COUNTER_START_VALUE … #define PERF_EVENT_UP_DOWN_THRESHOLD … /** * struct dmc_opp_table - Operating level desciption * @freq_hz: target frequency in Hz * @volt_uv: target voltage in uV * * Covers frequency and voltage settings of the DMC operating mode. */ struct dmc_opp_table { … }; /** * struct exynos5_dmc - main structure describing DMC device * @dev: DMC device * @df: devfreq device structure returned by devfreq framework * @gov_data: configuration of devfreq governor * @base_drexi0: DREX0 registers mapping * @base_drexi1: DREX1 registers mapping * @clk_regmap: regmap for clock controller registers * @lock: protects curr_rate and frequency/voltage setting section * @curr_rate: current frequency * @curr_volt: current voltage * @opp: OPP table * @opp_count: number of 'opp' elements * @timings_arr_size: number of 'timings' elements * @timing_row: values for timing row register, for each OPP * @timing_data: values for timing data register, for each OPP * @timing_power: balues for timing power register, for each OPP * @timings: DDR memory timings, from device tree * @min_tck: DDR memory minimum timing values, from device tree * @bypass_timing_row: value for timing row register for bypass timings * @bypass_timing_data: value for timing data register for bypass timings * @bypass_timing_power: value for timing power register for bypass * timings * @vdd_mif: Memory interface regulator * @fout_spll: clock: SPLL * @fout_bpll: clock: BPLL * @mout_spll: clock: mux SPLL * @mout_bpll: clock: mux BPLL * @mout_mclk_cdrex: clock: mux mclk_cdrex * @mout_mx_mspll_ccore: clock: mux mx_mspll_ccore * @counter: devfreq events * @num_counters: number of 'counter' elements * @last_overflow_ts: time (in ns) of last overflow of each DREX * @load: utilization in percents * @total: total time between devfreq events * @in_irq_mode: whether running in interrupt mode (true) * or polling (false) * * The main structure for the Dynamic Memory Controller which covers clocks, * memory regions, HW information, parameters and current operating mode. */ struct exynos5_dmc { … }; #define TIMING_FIELD(t_name, t_bit_beg, t_bit_end) … #define TIMING_VAL2REG(timing, t_val) … struct timing_reg { … }; static const struct timing_reg timing_row_reg_fields[] = …; static const struct timing_reg timing_data_reg_fields[] = …; static const struct timing_reg timing_power_reg_fields[] = …; #define TIMING_COUNT … static int exynos5_counters_set_event(struct exynos5_dmc *dmc) { … } static int exynos5_counters_enable_edev(struct exynos5_dmc *dmc) { … } static int exynos5_counters_disable_edev(struct exynos5_dmc *dmc) { … } /** * find_target_freq_idx() - Finds requested frequency in local DMC configuration * @dmc: device for which the information is checked * @target_rate: requested frequency in KHz * * Seeks in the local DMC driver structure for the requested frequency value * and returns index or error value. */ static int find_target_freq_idx(struct exynos5_dmc *dmc, unsigned long target_rate) { … } /** * exynos5_switch_timing_regs() - Changes bank register set for DRAM timings * @dmc: device for which the new settings is going to be applied * @set: boolean variable passing set value * * Changes the register set, which holds timing parameters. * There is two register sets: 0 and 1. The register set 0 * is used in normal operation when the clock is provided from main PLL. * The bank register set 1 is used when the main PLL frequency is going to be * changed and the clock is taken from alternative, stable source. * This function switches between these banks according to the * currently used clock source. */ static int exynos5_switch_timing_regs(struct exynos5_dmc *dmc, bool set) { … } /** * exynos5_init_freq_table() - Initialized PM OPP framework * @dmc: DMC device for which the frequencies are used for OPP init * @profile: devfreq device's profile * * Populate the devfreq device's OPP table based on current frequency, voltage. */ static int exynos5_init_freq_table(struct exynos5_dmc *dmc, struct devfreq_dev_profile *profile) { … } /** * exynos5_set_bypass_dram_timings() - Low-level changes of the DRAM timings * @dmc: device for which the new settings is going to be applied * * Low-level function for changing timings for DRAM memory clocking from * 'bypass' clock source (fixed frequency @400MHz). * It uses timing bank registers set 1. */ static void exynos5_set_bypass_dram_timings(struct exynos5_dmc *dmc) { … } /** * exynos5_dram_change_timings() - Low-level changes of the DRAM final timings * @dmc: device for which the new settings is going to be applied * @target_rate: target frequency of the DMC * * Low-level function for changing timings for DRAM memory operating from main * clock source (BPLL), which can have different frequencies. Thus, each * frequency must have corresponding timings register values in order to keep * the needed delays. * It uses timing bank registers set 0. */ static int exynos5_dram_change_timings(struct exynos5_dmc *dmc, unsigned long target_rate) { … } /** * exynos5_dmc_align_target_voltage() - Sets the final voltage for the DMC * @dmc: device for which it is going to be set * @target_volt: new voltage which is chosen to be final * * Function tries to align voltage to the safe level for 'normal' mode. * It checks the need of higher voltage and changes the value. The target * voltage might be lower that currently set and still the system will be * stable. */ static int exynos5_dmc_align_target_voltage(struct exynos5_dmc *dmc, unsigned long target_volt) { … } /** * exynos5_dmc_align_bypass_voltage() - Sets the voltage for the DMC * @dmc: device for which it is going to be set * @target_volt: new voltage which is chosen to be final * * Function tries to align voltage to the safe level for the 'bypass' mode. * It checks the need of higher voltage and changes the value. * The target voltage must not be less than currently needed, because * for current frequency the device might become unstable. */ static int exynos5_dmc_align_bypass_voltage(struct exynos5_dmc *dmc, unsigned long target_volt) { … } /** * exynos5_dmc_align_bypass_dram_timings() - Chooses and sets DRAM timings * @dmc: device for which it is going to be set * @target_rate: new frequency which is chosen to be final * * Function changes the DRAM timings for the temporary 'bypass' mode. */ static int exynos5_dmc_align_bypass_dram_timings(struct exynos5_dmc *dmc, unsigned long target_rate) { … } /** * exynos5_dmc_switch_to_bypass_configuration() - Switching to temporary clock * @dmc: DMC device for which the switching is going to happen * @target_rate: new frequency which is going to be set as a final * @target_volt: new voltage which is going to be set as a final * * Function configures DMC and clocks for operating in temporary 'bypass' mode. * This mode is used only temporary but if required, changes voltage and timings * for DRAM chips. It switches the main clock to stable clock source for the * period of the main PLL reconfiguration. */ static int exynos5_dmc_switch_to_bypass_configuration(struct exynos5_dmc *dmc, unsigned long target_rate, unsigned long target_volt) { … } /** * exynos5_dmc_change_freq_and_volt() - Changes voltage and frequency of the DMC * using safe procedure * @dmc: device for which the frequency is going to be changed * @target_rate: requested new frequency * @target_volt: requested voltage which corresponds to the new frequency * * The DMC frequency change procedure requires a few steps. * The main requirement is to change the clock source in the clk mux * for the time of main clock PLL locking. The assumption is that the * alternative clock source set as parent is stable. * The second parent's clock frequency is fixed to 400MHz, it is named 'bypass' * clock. This requires alignment in DRAM timing parameters for the new * T-period. There is two bank sets for keeping DRAM * timings: set 0 and set 1. The set 0 is used when main clock source is * chosen. The 2nd set of regs is used for 'bypass' clock. Switching between * the two bank sets is part of the process. * The voltage must also be aligned to the minimum required level. There is * this intermediate step with switching to 'bypass' parent clock source. * if the old voltage is lower, it requires an increase of the voltage level. * The complexity of the voltage manipulation is hidden in low level function. * In this function there is last alignment of the voltage level at the end. */ static int exynos5_dmc_change_freq_and_volt(struct exynos5_dmc *dmc, unsigned long target_rate, unsigned long target_volt) { … } /** * exynos5_dmc_get_volt_freq() - Gets the frequency and voltage from the OPP * table. * @dmc: device for which the frequency is going to be changed * @freq: requested frequency in KHz * @target_rate: returned frequency which is the same or lower than * requested * @target_volt: returned voltage which corresponds to the returned * frequency * @flags: devfreq flags provided for this frequency change request * * Function gets requested frequency and checks OPP framework for needed * frequency and voltage. It populates the values 'target_rate' and * 'target_volt' or returns error value when OPP framework fails. */ static int exynos5_dmc_get_volt_freq(struct exynos5_dmc *dmc, unsigned long *freq, unsigned long *target_rate, unsigned long *target_volt, u32 flags) { … } /** * exynos5_dmc_target() - Function responsible for changing frequency of DMC * @dev: device for which the frequency is going to be changed * @freq: requested frequency in KHz * @flags: flags provided for this frequency change request * * An entry function provided to the devfreq framework which provides frequency * change of the DMC. The function gets the possible rate from OPP table based * on requested frequency. It calls the next function responsible for the * frequency and voltage change. In case of failure, does not set 'curr_rate' * and returns error value to the framework. */ static int exynos5_dmc_target(struct device *dev, unsigned long *freq, u32 flags) { … } /** * exynos5_counters_get() - Gets the performance counters values. * @dmc: device for which the counters are going to be checked * @load_count: variable which is populated with counter value * @total_count: variable which is used as 'wall clock' reference * * Function which provides performance counters values. It sums up counters for * two DMC channels. The 'total_count' is used as a reference and max value. * The ratio 'load_count/total_count' shows the busy percentage [0%, 100%]. */ static int exynos5_counters_get(struct exynos5_dmc *dmc, unsigned long *load_count, unsigned long *total_count) { … } /** * exynos5_dmc_start_perf_events() - Setup and start performance event counters * @dmc: device for which the counters are going to be checked * @beg_value: initial value for the counter * * Function which enables needed counters, interrupts and sets initial values * then starts the counters. */ static void exynos5_dmc_start_perf_events(struct exynos5_dmc *dmc, u32 beg_value) { … } /** * exynos5_dmc_perf_events_calc() - Calculate utilization * @dmc: device for which the counters are going to be checked * @diff_ts: time between last interrupt and current one * * Function which calculates needed utilization for the devfreq governor. * It prepares values for 'busy_time' and 'total_time' based on elapsed time * between interrupts, which approximates utilization. */ static void exynos5_dmc_perf_events_calc(struct exynos5_dmc *dmc, u64 diff_ts) { … } /** * exynos5_dmc_perf_events_check() - Checks the status of the counters * @dmc: device for which the counters are going to be checked * * Function which is called from threaded IRQ to check the counters state * and to call approximation for the needed utilization. */ static void exynos5_dmc_perf_events_check(struct exynos5_dmc *dmc) { … } /** * exynos5_dmc_enable_perf_events() - Enable performance events * @dmc: device for which the counters are going to be checked * * Function which is setup needed environment and enables counters. */ static void exynos5_dmc_enable_perf_events(struct exynos5_dmc *dmc) { … } /** * exynos5_dmc_disable_perf_events() - Disable performance events * @dmc: device for which the counters are going to be checked * * Function which stops, disables performance event counters and interrupts. */ static void exynos5_dmc_disable_perf_events(struct exynos5_dmc *dmc) { … } /** * exynos5_dmc_get_status() - Read current DMC performance statistics. * @dev: device for which the statistics are requested * @stat: structure which has statistic fields * * Function reads the DMC performance counters and calculates 'busy_time' * and 'total_time'. To protect from overflow, the values are shifted right * by 10. After read out the counters are setup to count again. */ static int exynos5_dmc_get_status(struct device *dev, struct devfreq_dev_status *stat) { … } /** * exynos5_dmc_get_cur_freq() - Function returns current DMC frequency * @dev: device for which the framework checks operating frequency * @freq: returned frequency value * * It returns the currently used frequency of the DMC. The real operating * frequency might be lower when the clock source value could not be divided * to the requested value. */ static int exynos5_dmc_get_cur_freq(struct device *dev, unsigned long *freq) { … } /* * exynos5_dmc_df_profile - Devfreq governor's profile structure * * It provides to the devfreq framework needed functions and polling period. */ static struct devfreq_dev_profile exynos5_dmc_df_profile = …; /** * exynos5_dmc_align_init_freq() - Align initial frequency value * @dmc: device for which the frequency is going to be set * @bootloader_init_freq: initial frequency set by the bootloader in KHz * * The initial bootloader frequency, which is present during boot, might be * different that supported frequency values in the driver. It is possible * due to different PLL settings or used PLL as a source. * This function provides the 'initial_freq' for the devfreq framework * statistics engine which supports only registered values. Thus, some alignment * must be made. */ static unsigned long exynos5_dmc_align_init_freq(struct exynos5_dmc *dmc, unsigned long bootloader_init_freq) { … } /** * create_timings_aligned() - Create register values and align with standard * @dmc: device for which the frequency is going to be set * @reg_timing_row: array to fill with values for timing row register * @reg_timing_data: array to fill with values for timing data register * @reg_timing_power: array to fill with values for timing power register * @clk_period_ps: the period of the clock, known as tCK * * The function calculates timings and creates a register value ready for * a frequency transition. The register contains a few timings. They are * shifted by a known offset. The timing value is calculated based on memory * specyfication: minimal time required and minimal cycles required. */ static int create_timings_aligned(struct exynos5_dmc *dmc, u32 *reg_timing_row, u32 *reg_timing_data, u32 *reg_timing_power, u32 clk_period_ps) { … } /** * of_get_dram_timings() - helper function for parsing DT settings for DRAM * @dmc: device for which the frequency is going to be set * * The function parses DT entries with DRAM information. */ static int of_get_dram_timings(struct exynos5_dmc *dmc) { … } /** * exynos5_dmc_init_clks() - Initialize clocks needed for DMC operation. * @dmc: DMC structure containing needed fields * * Get the needed clocks defined in DT device, enable and set the right parents. * Read current frequency and initialize the initial rate for governor. */ static int exynos5_dmc_init_clks(struct exynos5_dmc *dmc) { … } /** * exynos5_performance_counters_init() - Initializes performance DMC's counters * @dmc: DMC for which it does the setup * * Initialization of performance counters in DMC for estimating usage. * The counter's values are used for calculation of a memory bandwidth and based * on that the governor changes the frequency. * The counters are not used when the governor is GOVERNOR_USERSPACE. */ static int exynos5_performance_counters_init(struct exynos5_dmc *dmc) { … } /** * exynos5_dmc_set_pause_on_switching() - Controls a pause feature in DMC * @dmc: device which is used for changing this feature * * There is a need of pausing DREX DMC when divider or MUX in clock tree * changes its configuration. In such situation access to the memory is blocked * in DMC automatically. This feature is used when clock frequency change * request appears and touches clock tree. */ static inline int exynos5_dmc_set_pause_on_switching(struct exynos5_dmc *dmc) { … } static irqreturn_t dmc_irq_thread(int irq, void *priv) { … } /** * exynos5_dmc_probe() - Probe function for the DMC driver * @pdev: platform device for which the driver is going to be initialized * * Initialize basic components: clocks, regulators, performance counters, etc. * Read out product version and based on the information setup * internal structures for the controller (frequency and voltage) and for DRAM * memory parameters: timings for each operating frequency. * Register new devfreq device for controlling DVFS of the DMC. */ static int exynos5_dmc_probe(struct platform_device *pdev) { … } /** * exynos5_dmc_remove() - Remove function for the platform device * @pdev: platform device which is going to be removed * * The function relies on 'devm' framework function which automatically * clean the device's resources. It just calls explicitly disable function for * the performance counters. */ static void exynos5_dmc_remove(struct platform_device *pdev) { … } static const struct of_device_id exynos5_dmc_of_match[] = …; MODULE_DEVICE_TABLE(of, exynos5_dmc_of_match); static struct platform_driver exynos5_dmc_platdrv = …; module_platform_driver(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …; MODULE_AUTHOR(…) …;