/* Copyright (c) 2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only 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. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msm_cpr.h" #define MODULE_NAME "msm-cpr" /** * Convert the Delay time to Timer Count Register * e.g if frequency is 19200 kHz and delay required is * 20000us, so timer count will be 19200 * 20000 / 1000 */ #define TIMER_COUNT(freq, delay) ((freq * delay) / 1000) #define ALL_CPR_IRQ 0x3F #define STEP_QUOT_MAX 25 #define STEP_QUOT_MIN 12 /* Need platform device handle for suspend and resume APIs */ static struct platform_device *cpr_pdev; static bool enable = 1; static bool disable_cpr; module_param(enable, bool, 0644); MODULE_PARM_DESC(enable, "CPR Enable"); static int msm_cpr_debug_mask; module_param_named( debug_mask, msm_cpr_debug_mask, int, S_IRUGO | S_IWUSR ); enum { /* configuration log */ MSM_CPR_DEBUG_CONFIG = BIT(0), /* step up/down interrupt log */ MSM_CPR_DEBUG_STEPS = BIT(1), /* cpu frequency notification log */ MSM_CPR_DEBUG_FREQ_TRANS = BIT(2), };\ #define msm_cpr_debug(mask, message, ...) \ do { \ if ((mask) & msm_cpr_debug_mask) \ pr_info(message, ##__VA_ARGS__); \ } while (0) struct msm_cpr { int curr_osc; int cpr_mode; int prev_mode; uint32_t floor; uint32_t ceiling; bool max_volt_set; void __iomem *base; unsigned int irq; uint32_t cur_Vmin; uint32_t cur_Vmax; uint32_t prev_volt_uV; struct mutex cpr_mutex; spinlock_t cpr_lock; struct regulator *vreg_cx; const struct msm_cpr_config *config; struct notifier_block freq_transition; uint32_t step_size; }; /* Need to maintain state data for suspend and resume APIs */ static struct msm_cpr_reg cpr_save_state; static inline void cpr_write_reg(struct msm_cpr *cpr, u32 offset, u32 value) { writel_relaxed(value, cpr->base + offset); } static inline u32 cpr_read_reg(struct msm_cpr *cpr, u32 offset) { return readl_relaxed(cpr->base + offset); } static void cpr_modify_reg(struct msm_cpr *cpr, u32 offset, u32 mask, u32 value) { u32 reg_val; reg_val = readl_relaxed(cpr->base + offset); reg_val &= ~mask; reg_val |= value; writel_relaxed(reg_val, cpr->base + offset); } #ifdef DEBUG static void cpr_regs_dump_all(struct msm_cpr *cpr) { pr_debug("RBCPR_GCNT_TARGET(%d): 0x%x\n", cpr->curr_osc, readl_relaxed(cpr->base + RBCPR_GCNT_TARGET(cpr->curr_osc))); pr_debug("RBCPR_TIMER_INTERVAL: 0x%x\n", readl_relaxed(cpr->base + RBCPR_TIMER_INTERVAL)); pr_debug("RBIF_TIMER_ADJUST: 0x%x\n", readl_relaxed(cpr->base + RBIF_TIMER_ADJUST)); pr_debug("RBIF_LIMIT: 0x%x\n", readl_relaxed(cpr->base + RBIF_LIMIT)); pr_debug("RBCPR_STEP_QUOT: 0x%x\n", readl_relaxed(cpr->base + RBCPR_STEP_QUOT)); pr_debug("RBIF_SW_VLEVEL: 0x%x\n", readl_relaxed(cpr->base + RBIF_SW_VLEVEL)); pr_debug("RBCPR_DEBUG1: 0x%x\n", readl_relaxed(cpr->base + RBCPR_DEBUG1)); pr_debug("RBCPR_RESULT_0: 0x%x\n", readl_relaxed(cpr->base + RBCPR_RESULT_0)); pr_debug("RBCPR_RESULT_1: 0x%x\n", readl_relaxed(cpr->base + RBCPR_RESULT_1)); pr_debug("RBCPR_QUOT_AVG: 0x%x\n", readl_relaxed(cpr->base + RBCPR_QUOT_AVG)); pr_debug("RBCPR_CTL: 0x%x\n", readl_relaxed(cpr->base + RBCPR_CTL)); pr_debug("RBIF_IRQ_EN(0): 0x%x\n", cpr_read_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line))); pr_debug("RBIF_IRQ_STATUS: 0x%x\n", cpr_read_reg(cpr, RBIF_IRQ_STATUS)); } #endif /* Enable the CPR H/W Block */ static void cpr_enable(struct msm_cpr *cpr) { spin_lock(&cpr->cpr_lock); cpr_modify_reg(cpr, RBCPR_CTL, LOOP_EN_M, ENABLE_CPR); spin_unlock(&cpr->cpr_lock); } /* Disable the CPR H/W Block */ static void cpr_disable(struct msm_cpr *cpr) { spin_lock(&cpr->cpr_lock); cpr_modify_reg(cpr, RBCPR_CTL, LOOP_EN_M, DISABLE_CPR); spin_unlock(&cpr->cpr_lock); } static int32_t cpr_poll_result(struct msm_cpr *cpr) { uint32_t val = 0; int8_t rc = 0; rc = readl_poll_timeout(cpr->base + RBCPR_RESULT_0, val, ~val & BUSY_M, 10, 1000); if (rc) pr_err("RBCPR_RESULT_0 read error: %d\n", rc); return rc; } static int32_t cpr_poll_result_done(struct msm_cpr *cpr) { uint32_t val = 0; int8_t rc = 0; rc = readl_poll_timeout(cpr->base + RBIF_IRQ_STATUS, val, val & 0x1, 10, 1000); if (rc) pr_err("RBCPR_IRQ_STATUS read error: %d\n", rc); return rc; } static void cpr_2pt_kv_analysis(struct msm_cpr *cpr, struct msm_cpr_mode *chip_data) { int32_t level_uV = 0, rc; uint32_t quot1, quot2; /** * 2 Point KV Analysis to calculate Step Quot * STEP_QUOT is number of QUOT units per PMIC step * STEP_QUOT = (quot1 - quot2) / 4 * * The step quot is calculated once for every mode and stored for * later use. */ if (chip_data->step_quot != ~0) goto out_2pt_kv; /** * Using the value from chip_data->tgt_volt_offset * calculate the new PMIC adjusted voltages and set * the PMIC to provide this value. * * Assuming default voltage is the highest value of safe boot up * voltage, offset is always subtracted from it. * */ level_uV = chip_data->turbo_Vmax - (chip_data->tgt_volt_offset * cpr->step_size); msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "tgt_volt_uV = %d\n", level_uV); /* Call the PMIC specific routine to set the voltage */ rc = regulator_set_voltage(cpr->vreg_cx, level_uV, level_uV); if (rc) { pr_err("Initial voltage set at %duV failed\n", level_uV); return; } rc = regulator_enable(cpr->vreg_cx); if (rc) { pr_err("failed to enable %s, rc=%d\n", "vdd_cx", rc); return; } /* First CPR measurement at a higher voltage to get QUOT1 */ /* Enable the Software mode of operation */ cpr_modify_reg(cpr, RBCPR_CTL, HW_TO_PMIC_EN_M, SW_MODE); /* Enable the cpr measurement */ cpr_modify_reg(cpr, RBCPR_CTL, LOOP_EN_M, ENABLE_CPR); /* IRQ is already disabled */ rc = cpr_poll_result_done(cpr); if (rc) { pr_err("Quot1: Exiting due to INT_DONE poll timeout\n"); return; } rc = cpr_poll_result(cpr); if (rc) { pr_err("Quot1: Exiting due to BUSY poll timeout\n"); return; } quot1 = (cpr_read_reg(cpr, RBCPR_DEBUG1) & QUOT_SLOW_M) >> 12; /* Take second CPR measurement at a lower voltage to get QUOT2 */ level_uV -= 4 * cpr->step_size; msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "tgt_volt_uV = %d\n", level_uV); cpr_modify_reg(cpr, RBCPR_CTL, LOOP_EN_M, DISABLE_CPR); /* Call the PMIC specific routine to set the voltage */ rc = regulator_set_voltage(cpr->vreg_cx, level_uV, level_uV); if (rc) { pr_err("Voltage set at %duV failed\n", level_uV); return; } cpr_modify_reg(cpr, RBCPR_CTL, HW_TO_PMIC_EN_M, SW_MODE); cpr_modify_reg(cpr, RBCPR_CTL, LOOP_EN_M, ENABLE_CPR); /* cpr_write_reg(cpr, RBIF_CONT_NACK_CMD, 0x1); */ rc = cpr_poll_result_done(cpr); if (rc) { pr_err("Quot2: Exiting due to INT_DONE poll timeout\n"); goto err_poll_result_done; } /* IRQ is already disabled */ rc = cpr_poll_result(cpr); if (rc) { pr_err("Quot2: Exiting due to BUSY poll timeout\n"); goto err_poll_result; } quot2 = (cpr_read_reg(cpr, RBCPR_DEBUG1) & QUOT_SLOW_M) >> 12; /* * Based on chip characterization data, it is good to add some * margin on top of calculated step quot to help reduce the * number of CPR interrupts. The present value suggested is 3. * Further, if the step quot is outside range, clamp it to the * maximum permitted value. */ chip_data->step_quot = ((quot1 - quot2) / 4) + 3; if (chip_data->step_quot < STEP_QUOT_MIN || chip_data->step_quot > STEP_QUOT_MAX) chip_data->step_quot = STEP_QUOT_MAX; msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "Step Quot is %d\n", chip_data->step_quot); /* Disable the cpr */ cpr_modify_reg(cpr, RBCPR_CTL, LOOP_EN_M, DISABLE_CPR); out_2pt_kv: /* Program the step quot */ cpr_write_reg(cpr, RBCPR_STEP_QUOT, (chip_data->step_quot & 0xFF)); return; err_poll_result: err_poll_result_done: regulator_disable(cpr->vreg_cx); } static inline void cpr_irq_clr_and_ack(struct msm_cpr *cpr, uint32_t mask) { /* Clear the interrupt */ cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); /* Acknowledge the Recommendation */ cpr_write_reg(cpr, RBIF_CONT_ACK_CMD, 0x1); } static inline void cpr_irq_clr_and_nack(struct msm_cpr *cpr, uint32_t mask) { cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); cpr_write_reg(cpr, RBIF_CONT_NACK_CMD, 0x1); } static void cpr_irq_set(struct msm_cpr *cpr, uint32_t irq, bool enable_irq) { uint32_t irq_enabled; irq_enabled = cpr_read_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line)); if (enable_irq == 1) irq_enabled |= irq; else irq_enabled &= ~irq; cpr_modify_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line), INT_MASK, irq_enabled); } static void cpr_up_event_handler(struct msm_cpr *cpr, uint32_t new_volt) { int set_volt_uV, rc; struct msm_cpr_mode *chip_data; chip_data = &cpr->config->cpr_mode_data[cpr->cpr_mode]; /* Set New PMIC voltage */ msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "current Vmin=%d Vmax=%d\n", cpr->cur_Vmin, cpr->cur_Vmax); set_volt_uV = (new_volt < cpr->cur_Vmax ? new_volt : cpr->cur_Vmax); if (cpr->prev_volt_uV == set_volt_uV) rc = regulator_sync_voltage(cpr->vreg_cx); else rc = regulator_set_voltage(cpr->vreg_cx, set_volt_uV, set_volt_uV); if (rc) { pr_err("Unable to set_voltage = %d, rc(%d)\n", set_volt_uV, rc); cpr_irq_clr_and_nack(cpr, BIT(4) | BIT(0)); return; } msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "(railway_voltage: %d uV)\n", set_volt_uV); cpr->prev_volt_uV = set_volt_uV; cpr->max_volt_set = (set_volt_uV == cpr->cur_Vmax) ? 1 : 0; /* Clear all the interrupts */ cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); /* Disable Auto ACK for Down interrupts */ cpr_modify_reg(cpr, RBCPR_CTL, SW_AUTO_CONT_NACK_DN_EN_M, 0); /* Enable down interrupts to App as it might have got disabled if CPR * hit Vmin earlier. Voltage set is above Vmin now. */ cpr_irq_set(cpr, DOWN_INT, 1); /* Acknowledge the Recommendation */ cpr_write_reg(cpr, RBIF_CONT_ACK_CMD, 0x1); } static void cpr_dn_event_handler(struct msm_cpr *cpr, uint32_t new_volt) { int set_volt_uV, rc; struct msm_cpr_mode *chip_data; chip_data = &cpr->config->cpr_mode_data[cpr->cpr_mode]; /* Set New PMIC volt */ set_volt_uV = (new_volt > cpr->cur_Vmin ? new_volt : cpr->cur_Vmin); if (cpr->prev_volt_uV == set_volt_uV) rc = regulator_sync_voltage(cpr->vreg_cx); else rc = regulator_set_voltage(cpr->vreg_cx, set_volt_uV, set_volt_uV); if (rc) { pr_err("Unable to set_voltage = %d, rc(%d)\n", set_volt_uV, rc); cpr_irq_clr_and_nack(cpr, BIT(2) | BIT(0)); return; } msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "(railway_voltage: %d uV)\n", set_volt_uV); cpr->prev_volt_uV = set_volt_uV; cpr->max_volt_set = 0; /* Clear all the interrupts */ cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); if (new_volt <= cpr->cur_Vmin) { /* * Disable down interrupt to App after we hit Vmin * It shall be enabled after we service an up interrupt * * A race condition between freq switch handler and CPR * interrupt handler is possible. So, do not disable * interrupt if a freq switch already caused a mode * change since we need this interrupt in the new mode. */ if (cpr->cpr_mode == cpr->prev_mode) { /* Enable Auto ACK for CPR Down Flags * while DOWN_INT to App is disabled */ cpr_modify_reg(cpr, RBCPR_CTL, SW_AUTO_CONT_NACK_DN_EN_M, SW_AUTO_CONT_NACK_DN_EN); cpr_irq_set(cpr, DOWN_INT, 0); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "DOWN_INT disabled\n"); } } /* Acknowledge the Recommendation */ cpr_write_reg(cpr, RBIF_CONT_ACK_CMD, 0x1); } static void cpr_set_vdd(struct msm_cpr *cpr, enum cpr_action action) { uint32_t curr_volt, new_volt, error_step; struct msm_cpr_mode *chip_data; chip_data = &cpr->config->cpr_mode_data[cpr->cpr_mode]; error_step = cpr_read_reg(cpr, RBCPR_RESULT_0) >> 2; msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "RBCPR_RESULT_0 17:6=%d\n", (cpr_read_reg(cpr, RBCPR_RESULT_0) >> 6) & 0xFFF); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "RBCPR_RESULT_0 Busy_b19=%d\n", (cpr_read_reg(cpr, RBCPR_RESULT_0) >> 19) & 0x1); error_step &= 0xF; curr_volt = regulator_get_voltage(cpr->vreg_cx); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "Current voltage=%d\n", curr_volt); if (action == UP) { /* Clear IRQ, ACK and return if Vdd already at Vmax */ if (cpr->max_volt_set == 1) { cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); cpr_write_reg(cpr, RBIF_CONT_NACK_CMD, 0x1); return; } /** * Using up margin in the comparison helps avoid having to * change up threshold values in chip register. */ if (error_step < (cpr->config->up_threshold + cpr->config->up_margin)) { msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "UP_INT error step too small to set\n"); cpr_irq_clr_and_nack(cpr, BIT(4) | BIT(0)); return; } /** * As per chip characterization recommendation, add a step * to up error steps to increase system stability */ error_step += 1; /* Calculte new PMIC voltage */ new_volt = curr_volt + (error_step * cpr->step_size); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "UP_INT: new_volt: %d, error_step=%d\n", new_volt, error_step); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "Current RBCPR_GCNT_TARGET(%d): = 0x%x\n", cpr->curr_osc, readl_relaxed(cpr->base + RBCPR_GCNT_TARGET(cpr->curr_osc)) & TARGET_M); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "(UP Voltage recommended by CPR: %d uV)\n", new_volt); cpr_up_event_handler(cpr, new_volt); } else if (action == DOWN) { /** * Using down margin in the comparison helps avoid having to * change down threshold values in chip register. */ if (error_step < (cpr->config->dn_threshold + cpr->config->dn_margin)) { msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "DOWN_INT error_step=%d is too small to set\n", error_step); cpr_irq_clr_and_nack(cpr, BIT(2) | BIT(0)); return; } /** * As per chip characterization recommendation, deduct 2 steps * from down error steps to decrease chances of getting closer * to the system level Vmin, thereby improving stability */ error_step -= 2; /* Keep down step upto two per interrupt to avoid any spike */ if (error_step > 2) error_step = 2; /* Calculte new PMIC voltage */ new_volt = curr_volt - (error_step * cpr->step_size); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "DOWN_INT: new_volt: %d, error_step=%d\n", new_volt, error_step); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "Current RBCPR_GCNT_TARGET(%d): = 0x%x\n", cpr->curr_osc, readl_relaxed(cpr->base + RBCPR_GCNT_TARGET(cpr->curr_osc)) & TARGET_M); msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "(DN Voltage recommended by CPR: %d uV)\n", new_volt); cpr_dn_event_handler(cpr, new_volt); } } static irqreturn_t cpr_irq0_handler(int irq, void *dev_id) { struct msm_cpr *cpr = dev_id; uint32_t reg_val, ctl_reg; reg_val = cpr_read_reg(cpr, RBIF_IRQ_STATUS); ctl_reg = cpr_read_reg(cpr, RBCPR_CTL); /* Following sequence of handling is as per each IRQ's priority */ if (reg_val & BIT(4)) { msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "CPR:IRQ %d occured for UP Flag\n", irq); cpr_set_vdd(cpr, UP); } else if ((reg_val & BIT(2)) && !(ctl_reg & SW_AUTO_CONT_NACK_DN_EN)) { msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "CPR:IRQ %d occured for Down Flag\n", irq); cpr_set_vdd(cpr, DOWN); } else if (reg_val & BIT(1)) { msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "CPR:IRQ %d occured for Min Flag\n", irq); cpr_irq_clr_and_nack(cpr, BIT(1) | BIT(0)); } else if (reg_val & BIT(5)) { msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "CPR:IRQ %d occured for MAX Flag\n", irq); cpr_irq_clr_and_nack(cpr, BIT(5) | BIT(0)); } else if (reg_val & BIT(3)) { /* SW_AUTO_CONT_ACK_EN is enabled */ msm_cpr_debug(MSM_CPR_DEBUG_STEPS, "CPR:IRQ %d occured for Mid Flag\n", irq); } return IRQ_HANDLED; } static void cpr_config(struct msm_cpr *cpr) { uint32_t delay_count, cnt = 0, rc; struct msm_cpr_mode *chip_data; chip_data = &cpr->config->cpr_mode_data[cpr->cpr_mode]; /* Program the SW vlevel */ cpr_modify_reg(cpr, RBIF_SW_VLEVEL, SW_VLEVEL_M, cpr->config->sw_vlevel); /* Set the floor and ceiling values */ cpr->floor = cpr->config->floor; cpr->ceiling = cpr->config->ceiling; /* Program the Ceiling & Floor values */ cpr_modify_reg(cpr, RBIF_LIMIT, (CEILING_M | FLOOR_M), ((cpr->ceiling << 6) | cpr->floor)); /* Program the Up and Down Threshold values */ cpr_modify_reg(cpr, RBCPR_CTL, UP_THRESHOLD_M | DN_THRESHOLD_M, cpr->config->up_threshold << 24 | cpr->config->dn_threshold << 28); cpr->curr_osc = chip_data->ring_osc; chip_data->ring_osc_data[cpr->curr_osc].quot = cpr->config->max_quot; /** * Program the gate count and target values * for all the ring oscilators */ while (cnt < NUM_OSC) { msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "Prog:cnt(%d) gcnt=0x%x quot=0x%x\n", cnt, chip_data->ring_osc_data[cnt].gcnt, chip_data->ring_osc_data[cnt].quot); cpr_modify_reg(cpr, RBCPR_GCNT_TARGET(cnt), (GCNT_M | TARGET_M), (chip_data->ring_osc_data[cnt].gcnt << 12 | chip_data->ring_osc_data[cnt].quot)); msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "RBCPR_GCNT_TARGET(%d): = 0x%x\n", cnt, readl_relaxed(cpr->base + RBCPR_GCNT_TARGET(cnt))); cnt++; } /* Configure the step quot */ cpr_2pt_kv_analysis(cpr, chip_data); /* Call the PMIC specific routine to set the voltage */ rc = regulator_set_voltage(cpr->vreg_cx, chip_data->calibrated_uV, chip_data->calibrated_uV); if (rc) pr_err("Voltage set failed %d\n", rc); /* * Program the Timer Register for delay between CPR measurements * This is required to allow the device sufficient time for idle * power collapse. */ delay_count = TIMER_COUNT(cpr->config->ref_clk_khz, cpr->config->delay_us); cpr_write_reg(cpr, RBCPR_TIMER_INTERVAL, delay_count); /* Use Consecutive Down to avoid any interrupt due to spike */ cpr_write_reg(cpr, RBIF_TIMER_ADJUST, (0x2 << RBIF_CONS_DN_SHIFT)); msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "RBIF_TIMER_ADJUST: 0x%x\n", readl_relaxed(cpr->base + RBIF_TIMER_ADJUST)); /* Enable the Timer */ cpr_modify_reg(cpr, RBCPR_CTL, TIMER_M, ENABLE_TIMER); /* Enable Auto ACK for Mid interrupts */ cpr_modify_reg(cpr, RBCPR_CTL, SW_AUTO_CONT_ACK_EN_M, SW_AUTO_CONT_ACK_EN); } static int cpr_freq_transition(struct notifier_block *nb, unsigned long val, void *data) { struct msm_cpr *cpr = container_of(nb, struct msm_cpr, freq_transition); struct cpufreq_freqs *freqs = data; uint32_t quot, new_freq, ctl_reg; switch (val) { case CPUFREQ_PRECHANGE: msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "pre freq change notification to cpr\n"); /* Disable Measurement to stop generation of CPR IRQs */ cpr_disable(cpr); /* Disable routing of IRQ to App */ cpr_irq_set(cpr, INT_MASK & ~MID_INT, 0); disable_irq(cpr->irq); cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "RBCPR_CTL: 0x%x\n", readl_relaxed(cpr->base + RBCPR_CTL)); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "RBIF_IRQ_STATUS: 0x%x\n", cpr_read_reg(cpr, RBIF_IRQ_STATUS)); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "RBIF_IRQ_EN(0): 0x%x\n", cpr_read_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line))); cpr->prev_mode = cpr->cpr_mode; break; case CPUFREQ_POSTCHANGE: pr_debug("post freq change notification to cpr\n"); ctl_reg = cpr_read_reg(cpr, RBCPR_CTL); /** * As per chip characterization data, use max nominal freq * to calculate quot for all lower frequencies too */ if (freqs->new > cpr->config->max_nom_freq) { new_freq = freqs->new; cpr->cur_Vmin = cpr->config->cpr_mode_data[1].turbo_Vmin; cpr->cur_Vmax = cpr->config->cpr_mode_data[1].turbo_Vmax; } else { new_freq = cpr->config->max_nom_freq; cpr->cur_Vmin = cpr->config->cpr_mode_data[1].nom_Vmin; cpr->cur_Vmax = cpr->config->cpr_mode_data[1].nom_Vmax; } /* Configure CPR for the new frequency */ quot = cpr->config->get_quot(cpr->config->max_quot, cpr->config->max_freq / 1000, new_freq / 1000); cpr_modify_reg(cpr, RBCPR_GCNT_TARGET(cpr->curr_osc), TARGET_M, quot); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "RBCPR_GCNT_TARGET(%d): = 0x%x\n", cpr->curr_osc, readl_relaxed(cpr->base + RBCPR_GCNT_TARGET(cpr->curr_osc))); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "new_freq: %d, quot_freq: %d, quot: %d\n", freqs->new, new_freq, quot); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "PVS Voltage setting is: %d\n", regulator_get_voltage(cpr->vreg_cx)); enable_irq(cpr->irq); /** * Enable all interrupts. One of them could be in a disabled * state if vdd had hit Vmax / Vmin earlier */ cpr_irq_set(cpr, INT_MASK & ~MID_INT, 1); /** * Clear the auto NACK down bit if enabled in the freq. * transition phase. */ if (ctl_reg & SW_AUTO_CONT_NACK_DN_EN) cpr_modify_reg(cpr, RBCPR_CTL, SW_AUTO_CONT_NACK_DN_EN_M, 0); if (cpr->max_volt_set) cpr->max_volt_set = 0; msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "RBIF_IRQ_EN(0): 0x%x\n", cpr_read_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line))); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "RBCPR_CTL: 0x%x\n", readl_relaxed(cpr->base + RBCPR_CTL)); msm_cpr_debug(MSM_CPR_DEBUG_FREQ_TRANS, "RBIF_IRQ_STATUS: 0x%x\n", cpr_read_reg(cpr, RBIF_IRQ_STATUS)); /* Clear all the interrupts */ cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); cpr_enable(cpr); break; default: break; } return NOTIFY_OK; } #ifdef CONFIG_PM static int msm_cpr_resume(struct device *dev) { struct msm_cpr *cpr = dev_get_drvdata(dev); int osc_num = cpr->config->cpr_mode_data->ring_osc; cpr->config->clk_enable(); cpr_write_reg(cpr, RBCPR_TIMER_INTERVAL, cpr_save_state.rbif_timer_interval); cpr_write_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line), cpr_save_state.rbif_int_en); cpr_write_reg(cpr, RBIF_LIMIT, cpr_save_state.rbif_limit); cpr_write_reg(cpr, RBIF_TIMER_ADJUST, cpr_save_state.rbif_timer_adjust); cpr_write_reg(cpr, RBCPR_GCNT_TARGET(osc_num), cpr_save_state.rbcpr_gcnt_target); cpr_write_reg(cpr, RBCPR_STEP_QUOT, cpr_save_state.rbcpr_step_quot); cpr_write_reg(cpr, RBIF_SW_VLEVEL, cpr_save_state.rbif_sw_level); cpr_write_reg(cpr, RBCPR_CTL, cpr_save_state.rbcpr_ctl); /* Clear all the interrupts */ cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); enable_irq(cpr->irq); cpr_enable(cpr); return 0; } static int msm_cpr_suspend(struct device *dev) { struct msm_cpr *cpr = dev_get_drvdata(dev); int osc_num = cpr->config->cpr_mode_data->ring_osc; /* Disable CPR measurement before IRQ to avoid pending interrupts */ cpr_disable(cpr); disable_irq(cpr->irq); /* Clear all the interrupts */ cpr_write_reg(cpr, RBIF_IRQ_CLEAR, ALL_CPR_IRQ); cpr_save_state.rbif_timer_interval = cpr_read_reg(cpr, RBCPR_TIMER_INTERVAL); cpr_save_state.rbif_int_en = cpr_read_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line)); cpr_save_state.rbif_limit = cpr_read_reg(cpr, RBIF_LIMIT); cpr_save_state.rbif_timer_adjust = cpr_read_reg(cpr, RBIF_TIMER_ADJUST); cpr_save_state.rbcpr_gcnt_target = cpr_read_reg(cpr, RBCPR_GCNT_TARGET(osc_num)); cpr_save_state.rbcpr_step_quot = cpr_read_reg(cpr, RBCPR_STEP_QUOT); cpr_save_state.rbif_sw_level = cpr_read_reg(cpr, RBIF_SW_VLEVEL); cpr_save_state.rbcpr_ctl = cpr_read_reg(cpr, RBCPR_CTL); return 0; } void msm_cpr_pm_resume(void) { if (!enable || disable_cpr) return; msm_cpr_resume(&cpr_pdev->dev); } EXPORT_SYMBOL(msm_cpr_pm_resume); void msm_cpr_pm_suspend(void) { if (!enable || disable_cpr) return; msm_cpr_suspend(&cpr_pdev->dev); } EXPORT_SYMBOL(msm_cpr_pm_suspend); #endif void msm_cpr_disable(void) { struct msm_cpr *cpr; if (!enable || disable_cpr) return; cpr = platform_get_drvdata(cpr_pdev); cpr_disable(cpr); } EXPORT_SYMBOL(msm_cpr_disable); void msm_cpr_enable(void) { struct msm_cpr *cpr; if (!enable || disable_cpr) return; cpr = platform_get_drvdata(cpr_pdev); cpr_enable(cpr); } EXPORT_SYMBOL(msm_cpr_enable); static int __devinit msm_cpr_probe(struct platform_device *pdev) { int res, irqn, irq_enabled; struct msm_cpr *cpr; const struct msm_cpr_config *pdata = pdev->dev.platform_data; void __iomem *base; struct resource *mem; struct msm_cpr_mode *chip_data; if (!enable) return -EPERM; if (!pdata) { pr_err("CPR: Platform data is not available\n"); enable = false; return -EIO; } if (pdata->disable_cpr == true) { pr_err("CPR disabled by modem\n"); disable_cpr = true; return -EPERM; } cpr = devm_kzalloc(&pdev->dev, sizeof(struct msm_cpr), GFP_KERNEL); if (!cpr) { enable = false; return -ENOMEM; } /* enable clk for cpr */ if (!pdata->clk_enable) { pr_err("CPR: Invalid clk_enable hook\n"); return -EFAULT; } pdata->clk_enable(); /* Initialize platform_data */ cpr->config = pdata; /* Set initial Vmin,Vmax equal to turbo */ cpr->cur_Vmin = cpr->config->cpr_mode_data[1].turbo_Vmin; cpr->cur_Vmax = cpr->config->cpr_mode_data[1].turbo_Vmax; cpr_pdev = pdev; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem || !mem->start) { pr_err("CPR: get resource failed\n"); res = -ENXIO; goto out; } base = ioremap_nocache(mem->start, resource_size(mem)); if (!base) { pr_err("CPR: ioremap failed\n"); res = -ENOMEM; goto out; } if (cpr->config->irq_line < 0) { pr_err("CPR: Invalid IRQ line specified\n"); res = -ENXIO; goto err_ioremap; } irqn = platform_get_irq(pdev, cpr->config->irq_line); if (irqn < 0) { pr_err("CPR: Unable to get irq\n"); res = -ENXIO; goto err_ioremap; } cpr->irq = irqn; cpr->base = base; cpr->step_size = pdata->step_size; spin_lock_init(&cpr->cpr_lock); /* Initialize the Voltage domain for CPR */ cpr->vreg_cx = regulator_get(&pdev->dev, "vddx_cx"); if (IS_ERR(cpr->vreg_cx)) { res = PTR_ERR(cpr->vreg_cx); pr_err("could not get regulator: %d\n", res); goto err_reg_get; } /* Assume current mode is TURBO Mode */ cpr->cpr_mode = TURBO_MODE; cpr->prev_mode = TURBO_MODE; /* Initial configuration of CPR */ cpr_config(cpr); platform_set_drvdata(pdev, cpr); chip_data = &cpr->config->cpr_mode_data[cpr->cpr_mode]; msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "CPR Platform Data (upside_steps: %d) (downside_steps: %d))", cpr->config->up_threshold, cpr->config->dn_threshold); msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "(nominal_voltage: %duV) (turbo_voltage: %duV)\n", cpr->config->cpr_mode_data[NORMAL_MODE].calibrated_uV, cpr->config->cpr_mode_data[TURBO_MODE].calibrated_uV); msm_cpr_debug(MSM_CPR_DEBUG_CONFIG, "(Current corner: TURBO) (gcnt_target: %d) (quot: %d)\n", chip_data->ring_osc_data[chip_data->ring_osc].gcnt, chip_data->ring_osc_data[chip_data->ring_osc].quot); /* Initialze the Debugfs Entry for cpr */ res = msm_cpr_debug_init(cpr->base); if (res) { pr_err("CPR: Debugfs Creation Failed\n"); goto err_ioremap; } /* Register the interrupt handler for IRQ 0 */ res = request_threaded_irq(irqn, NULL, cpr_irq0_handler, IRQF_TRIGGER_RISING, "msm-cpr-irq0", cpr); if (res) { pr_err("CPR: request irq failed for IRQ %d\n", irqn); goto err_ioremap; } /** * Enable the requested interrupt lines. * Do not enable MID_INT since we shall use * SW_AUTO_CONT_ACK_EN bit. */ irq_enabled = INT_MASK & ~MID_INT; cpr_modify_reg(cpr, RBIF_IRQ_EN(cpr->config->irq_line), INT_MASK, irq_enabled); /* Enable the cpr */ cpr_modify_reg(cpr, RBCPR_CTL, LOOP_EN_M, ENABLE_CPR); cpr->freq_transition.notifier_call = cpr_freq_transition; cpufreq_register_notifier(&cpr->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); pr_info("MSM CPR driver successfully registered!\n"); return res; err_reg_get: free_irq(irqn, cpr); err_ioremap: iounmap(base); out: enable = false; return res; } static int __devexit msm_cpr_remove(struct platform_device *pdev) { struct msm_cpr *cpr = platform_get_drvdata(pdev); cpufreq_unregister_notifier(&cpr->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); regulator_disable(cpr->vreg_cx); regulator_put(cpr->vreg_cx); free_irq(cpr->irq, cpr); iounmap(cpr->base); platform_set_drvdata(pdev, NULL); return 0; } static const struct dev_pm_ops msm_cpr_dev_pm_ops = { .suspend = msm_cpr_suspend, .resume = msm_cpr_resume, }; static struct platform_driver msm_cpr_driver = { .probe = msm_cpr_probe, .remove = __devexit_p(msm_cpr_remove), .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &msm_cpr_dev_pm_ops, #endif }, }; static int __init msm_init_cpr(void) { return platform_driver_register(&msm_cpr_driver); } module_init(msm_init_cpr); static void __exit msm_exit_cpr(void) { platform_driver_unregister(&msm_cpr_driver); } module_exit(msm_exit_cpr); MODULE_DESCRIPTION("MSM CPR Driver"); MODULE_VERSION("1.0"); MODULE_LICENSE("GPL v2");