| /* Copyright (c) 2013, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation, Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <bits.h> |
| #include <debug.h> |
| #include <reg.h> |
| #include <spmi.h> |
| #include <platform/timer.h> |
| #include <pm8x41_adc.h> |
| #include <pm8x41_hw.h> |
| |
| /* |
| * This is the predefined adc configuration values for the supported |
| * channels |
| */ |
| static struct adc_conf adc_data[] = { |
| CHAN_INIT(VADC_USR1_BASE, VADC_BAT_CHAN_ID, VADC_MODE_NORMAL, VADC_DECIM_RATIO_VAL, HW_SET_DELAY_100US, FAST_AVG_SAMP_1, CALIB_RATIO), |
| CHAN_INIT(VADC_USR1_BASE, VADC_BAT_VOL_CHAN_ID, VADC_MODE_NORMAL, VADC_DECIM_RATIO_VAL, HW_SET_DELAY_100US, FAST_AVG_SAMP_1, CALIB_ABS), |
| CHAN_INIT(VADC_USR1_BASE, MPP_8_CHAN_ID, VADC_MODE_NORMAL, VADC_DECIM_RATIO_VAL, HW_SET_DELAY_100US, FAST_AVG_SAMP_1, CALIB_ABS), |
| CHAN_INIT(VADC_USR2_BASE, MPP_1_CHAN_ID, VADC_MODE_NORMAL, VADC_DECIM_RATIO_VAL, HW_SET_DELAY_100US, FAST_AVG_SAMP_1, CALIB_RATIO), |
| }; |
| |
| |
| static struct adc_conf* get_channel_prop(uint16_t ch_num) |
| { |
| struct adc_conf *chan_data = NULL; |
| uint8_t i; |
| |
| for(i = 0; i < ARRAY_SIZE(adc_data) ; i++) { |
| chan_data = &adc_data[i]; |
| if (chan_data->chan == ch_num) |
| break; |
| } |
| |
| return chan_data; |
| } |
| |
| static void adc_limit_result_range(uint16_t *result) |
| { |
| if (*result < VADC_MIN_VAL) |
| *result = VADC_MIN_VAL; |
| else if(*result > VADC_MAX_VAL) |
| *result = VADC_MAX_VAL; |
| } |
| |
| static void adc_read_conv_result(struct adc_conf *adc, uint16_t *result) |
| { |
| uint8_t val = 0; |
| |
| /* Read the MSB part */ |
| val = REG_READ(adc->base + VADC_REG_DATA_MSB); |
| *result = val; |
| |
| /* Read the LSB part */ |
| val = REG_READ(adc->base + VADC_REG_DATA_LSB); |
| *result = ((*result) << 8) | val; |
| |
| adc_limit_result_range(result); |
| } |
| |
| static void adc_enable(struct adc_conf *adc, uint8_t enable) |
| { |
| if (enable) |
| REG_WRITE((adc->base + VADC_EN_CTL), VADC_CTL_EN_BIT); |
| else |
| REG_WRITE((adc->base + VADC_EN_CTL), (uint8_t) (~VADC_CTL_EN_BIT)); |
| } |
| |
| static void adc_measure(struct adc_conf *adc, uint16_t *result) |
| { |
| uint8_t status; |
| |
| /* Request conversion */ |
| REG_WRITE((adc->base + VADC_CONV_REQ), VADC_CON_REQ_BIT); |
| |
| /* Poll for the conversion to complete */ |
| do { |
| status = REG_READ(adc->base + VADC_STATUS); |
| status &= VADC_STATUS_MASK; |
| if (status == VADC_STATUS_EOC) { |
| dprintf(SPEW, "ADC conversion is complete\n"); |
| break; |
| } |
| /* Wait for sometime before polling for the status again */ |
| udelay(10); |
| } while(1); |
| |
| /* Now read the conversion result */ |
| adc_read_conv_result(adc, result); |
| } |
| |
| /* |
| * This function configures adc & requests for conversion |
| */ |
| static uint16_t adc_configure(struct adc_conf *adc) |
| { |
| uint8_t mode; |
| uint8_t adc_p; |
| uint16_t result; |
| |
| /* Mode Selection */ |
| mode = (adc->mode << VADC_MODE_BIT_NORMAL); |
| REG_WRITE((adc->base + VADC_MODE_CTRL), mode); |
| |
| /* Select Channel */ |
| REG_WRITE((adc->base + VADC_CHAN_SEL), adc->chan); |
| |
| /* ADC digital param setup */ |
| adc_p = (adc->adc_param << VADC_DECIM_RATIO_SEL); |
| REG_WRITE((adc->base + VADC_DIG_ADC_PARAM), adc_p); |
| |
| /* hardware settling time */ |
| REG_WRITE((adc->base + VADC_HW_SETTLE_TIME), adc->hw_set_time); |
| |
| /* For normal mode set the fast avg */ |
| REG_WRITE((adc->base + VADC_FAST_AVG), adc->fast_avg); |
| |
| /* Enable Vadc */ |
| adc_enable(adc, true); |
| |
| /* Measure the result */ |
| adc_measure(adc, &result); |
| |
| /* Disable vadc */ |
| adc_enable(adc, false); |
| |
| return result; |
| } |
| |
| static uint32_t vadc_calibrate(uint16_t result, struct adc_conf* adc) |
| { |
| struct adc_conf calib; |
| uint16_t calib1; |
| uint16_t calib2; |
| uint32_t calib_result = 0; |
| uint32_t mul; |
| |
| if(adc->calib_type == CALIB_ABS) { |
| /* |
| * Measure the calib data for 1.25 V ref |
| */ |
| calib.base = adc->base; |
| calib.chan = VREF_125_CHAN_ID; |
| calib.mode = VADC_MODE_NORMAL; |
| calib.adc_param = VADC_DECIM_RATIO_VAL; |
| calib.hw_set_time = 0x0; |
| calib.fast_avg = 0x0; |
| |
| calib1 = adc_configure(&calib); |
| |
| /* |
| * Measure the calib data for 0.625 V ref |
| */ |
| calib.base = adc->base; |
| calib.chan = VREF_625_CHAN_ID; |
| calib.mode = VADC_MODE_NORMAL; |
| calib.adc_param = VADC_DECIM_RATIO_VAL; |
| calib.hw_set_time = 0x0; |
| calib.fast_avg = 0x0; |
| |
| calib2 = adc_configure(&calib); |
| |
| mul = VREF_625_MV / (calib1 - calib2); |
| calib_result = (result - calib2) * mul; |
| calib_result += VREF_625_MV; |
| calib_result *= OFFSET_GAIN_DNOM; |
| calib_result /= OFFSET_GAIN_NUME; |
| } else if(adc->calib_type == CALIB_RATIO) { |
| /* |
| * Measure the calib data for VDD_ADC ref |
| */ |
| calib.base = adc->base; |
| calib.chan = VDD_VADC_CHAN_ID; |
| calib.mode = VADC_MODE_NORMAL; |
| calib.adc_param = VADC_DECIM_RATIO_VAL; |
| calib.hw_set_time = 0; |
| calib.fast_avg = 0; |
| |
| calib1 = adc_configure(&calib); |
| |
| /* |
| * Measure the calib data for ADC_GND |
| */ |
| calib.base = adc->base; |
| calib.chan = GND_REF_CHAN_ID; |
| calib.mode = VADC_MODE_NORMAL; |
| calib.adc_param = VADC_DECIM_RATIO_VAL; |
| calib.hw_set_time = 0; |
| calib.fast_avg = 0; |
| |
| calib2 = adc_configure(&calib); |
| |
| mul = VREF_18_V / (calib1 - calib2); |
| calib_result = (result - calib2) * mul; |
| } |
| |
| return calib_result; |
| } |
| |
| /* |
| * This API takes channel number as input |
| * & returns the calibrated voltage as output |
| * The calibrated result is the voltage in uVs |
| */ |
| uint32_t pm8x41_adc_channel_read(uint16_t ch_num) |
| { |
| struct adc_conf *adc; |
| uint16_t result; |
| uint32_t calib_result; |
| |
| adc = get_channel_prop(ch_num); |
| if (!adc) { |
| dprintf(CRITICAL, "Error: requested channel is not supported: %u\n", ch_num); |
| return 0; |
| } |
| |
| result = adc_configure(adc); |
| |
| calib_result = vadc_calibrate(result, adc); |
| |
| dprintf(SPEW, "Result: Raw %u\tCalibrated:%u\n", result, calib_result); |
| |
| return calib_result; |
| } |
| |
| /* |
| * This function configures the maximum |
| * current for USB in uA |
| */ |
| int pm8x41_iusb_max_config(uint32_t current) |
| { |
| uint32_t mul; |
| |
| if(current < IUSB_MIN_UA || current > IUSB_MAX_UA) { |
| dprintf(CRITICAL, "Error: Current value for USB are not in permissible range\n"); |
| return -1; |
| } |
| |
| if (current == USB_CUR_100UA) |
| mul = 0x0; |
| else if (current == USB_CUR_150UA) |
| mul = 0x1; |
| else |
| mul = current / USB_CUR_100UA; |
| |
| REG_WRITE(IUSB_MAX_REG, mul); |
| |
| return 0; |
| } |
| |
| /* |
| * This function configures the maximum |
| * current for battery in uA |
| */ |
| int pm8x41_ibat_max_config(uint32_t current) |
| { |
| uint32_t mul; |
| |
| if(current < IBAT_MIN_UA || current > IBAT_MAX_UA) { |
| dprintf(CRITICAL, "Error: Current value for BAT are not in permissible range\n"); |
| return -1; |
| } |
| |
| mul = (current - BAT_CUR_100UA) / BAT_CUR_STEP; |
| |
| REG_WRITE(IBAT_MAX_REG, mul); |
| |
| return 0; |
| } |
| |
| /* |
| * API: pm8x41_chgr_vdd_max_config |
| * Configure the VDD max to i/p value |
| */ |
| int pm8x41_chgr_vdd_max_config(uint32_t vol) |
| { |
| uint8_t mul; |
| |
| /* Check for permissible range of i/p */ |
| if (vol < VDD_MIN_UA || vol > VDD_MAX_UA) |
| { |
| dprintf(CRITICAL, "Error: Voltage values are not in permissible range\n"); |
| return -1; |
| } |
| |
| /* Calculate the multiplier */ |
| mul = (vol - VDD_MIN_UA) / VDD_VOL_STEP; |
| |
| /* Write to VDD_MAX register */ |
| REG_WRITE(CHGR_VDD_MAX, mul); |
| |
| return 0; |
| } |
| |
| /* |
| * API: pm8x41_chgr_ctl_enable |
| * Enable FSM-controlled autonomous charging |
| */ |
| int pm8x41_chgr_ctl_enable(uint8_t enable) |
| { |
| /* If charging has to be enabled? |
| * 1. Enable charging in charge control |
| * 2. Enable boot done to enable charging |
| */ |
| if (enable) |
| { |
| REG_WRITE(CHGR_CHG_CTRL, (CHGR_ENABLE << CHGR_EN_BIT)); |
| REG_WRITE(MISC_BOOT_DONE, (BOOT_DONE << BOOT_DONE_BIT)); |
| } |
| else |
| REG_WRITE(CHGR_CHG_CTRL, CHGR_DISABLE); |
| |
| return 0; |
| } |
| |
| /* |
| * API: pm8x41_get_batt_voltage |
| * Get calibrated battery voltage from VADC, in UV |
| */ |
| uint32_t pm8x41_get_batt_voltage() |
| { |
| uint32_t voltage; |
| |
| voltage = pm8x41_adc_channel_read(VADC_BAT_VOL_CHAN_ID); |
| |
| if(!voltage) |
| { |
| dprintf(CRITICAL, "Error getting battery Voltage\n"); |
| return 0; |
| } |
| |
| return voltage; |
| } |
| |
| /* |
| * API: pm8x41_get_voltage_based_soc |
| * Get voltage based State Of Charge, this takes vdd max & battery cutoff |
| * voltage as i/p in uV |
| */ |
| uint32_t pm8x41_get_voltage_based_soc(uint32_t cutoff_vol, uint32_t vdd_max) |
| { |
| uint32_t vol_soc; |
| uint32_t batt_vol; |
| |
| batt_vol = pm8x41_get_batt_voltage(); |
| |
| if(!batt_vol) |
| { |
| dprintf(CRITICAL, "Error: Getting battery voltage based Soc\n"); |
| return 0; |
| } |
| |
| if (cutoff_vol >= vdd_max) |
| { |
| dprintf(CRITICAL, "Cutoff is greater than VDD max, Voltage based soc can't be calculated\n"); |
| return 0; |
| } |
| |
| vol_soc = ((batt_vol - cutoff_vol) * 100) / (vdd_max - cutoff_vol); |
| |
| return vol_soc; |
| } |
| |
| /* |
| * API: pm8x41_enable_mpp_as_adc |
| * Configurate the MPP pin as the ADC feature. |
| * mpp_num starts from 0. |
| */ |
| void pm8x41_enable_mpp_as_adc(uint16_t mpp_num) |
| { |
| uint32_t val; |
| uint32_t amux_offset; |
| if(mpp_num > MPP_MAX_NUM) |
| { |
| dprintf(CRITICAL, "Error: The MPP pin number is unavailable\n"); |
| return; |
| } |
| /* set the MPP mode as AIN */ |
| val = (MPP_MODE_AIN << Q_REG_MODE_SEL_SHIFT) \ |
| | (0x1 << Q_REG_OUT_INVERT_SHIFT) \ |
| | (0x0 << Q_REG_SRC_SEL_SHIFT); |
| REG_WRITE((MPP_REG_BASE + mpp_num * MPP_REG_RANGE + Q_REG_MODE_CTL), |
| val); |
| |
| /* Enable the MPP */ |
| val = (MPP_MASTER_ENABLE << Q_REG_MASTER_EN_SHIFT); |
| REG_WRITE((MPP_REG_BASE + mpp_num * MPP_REG_RANGE + Q_REG_EN_CTL), val); |
| |
| /* AIN route to related AMUX */ |
| amux_offset = mpp_num % 4; |
| val = (amux_offset << Q_REG_AIN_ROUTE_SHIFT); |
| REG_WRITE((MPP_REG_BASE + mpp_num * MPP_REG_RANGE + Q_REG_AIN_CTL), |
| val); |
| } |
| |
| /* |
| * API: pm8950_enable_mpp_as_adc |
| * Configurate the MPP pin as the ADC feature. |
| * mpp_num starts from 0. |
| */ |
| void pm8950_enable_mpp_as_adc(uint16_t mpp_num) |
| { |
| uint32_t val; |
| uint32_t amux_offset; |
| if(mpp_num > MPP_MAX_NUM) |
| { |
| dprintf(CRITICAL, "Error: The MPP pin number is unavailable\n"); |
| return; |
| } |
| /* set the MPP mode as AIN */ |
| val = (MPP_MODE_AIN << Q_REG_MODE_SEL_SHIFT) \ |
| | (0x1 << Q_REG_OUT_INVERT_SHIFT) \ |
| | (0x0 << Q_REG_SRC_SEL_SHIFT); |
| REG_WRITE((PM8950_SID * SID_REG_BASE + MPP_REG_BASE + |
| mpp_num * MPP_REG_RANGE + Q_REG_MODE_CTL), val); |
| |
| /* Enable the MPP */ |
| val = (MPP_MASTER_ENABLE << Q_REG_MASTER_EN_SHIFT); |
| REG_WRITE((PM8950_SID * SID_REG_BASE + MPP_REG_BASE + |
| mpp_num * MPP_REG_RANGE + Q_REG_EN_CTL), val); |
| |
| /* AIN route to related AMUX */ |
| amux_offset = mpp_num % 4; |
| val = (amux_offset << Q_REG_AIN_ROUTE_SHIFT); |
| REG_WRITE((PM8950_SID * SID_REG_BASE + MPP_REG_BASE + |
| mpp_num * MPP_REG_RANGE + Q_REG_AIN_CTL), val); |
| } |
| |
| /* |
| * API: pmi8950_enable_mpp_as_adc |
| * Configurate the MPP pin as the ADC feature. |
| * mpp_num starts from 0. |
| * only MPP1 can be used as ADC IN. |
| */ |
| void pmi8950_enable_mpp_as_adc(uint16_t mpp_num) |
| { |
| uint32_t val; |
| uint32_t amux_offset; |
| if(mpp_num > MPP_MAX_NUM) |
| { |
| dprintf(CRITICAL, "Error: The MPP pin number is unavailable\n"); |
| return; |
| } |
| /* set the MPP mode as AIN */ |
| val = (MPP_MODE_AIN << Q_REG_MODE_SEL_SHIFT) \ |
| | (0x1 << Q_REG_OUT_INVERT_SHIFT) \ |
| | (0x0 << Q_REG_SRC_SEL_SHIFT); |
| REG_WRITE((PMI8950_SID * SID_REG_BASE + MPP_REG_BASE + |
| mpp_num * MPP_REG_RANGE + Q_REG_MODE_CTL), val); |
| |
| /* Enable the MPP */ |
| val = (MPP_MASTER_ENABLE << Q_REG_MASTER_EN_SHIFT); |
| REG_WRITE((PMI8950_SID * SID_REG_BASE + MPP_REG_BASE + |
| mpp_num * MPP_REG_RANGE + Q_REG_EN_CTL), val); |
| |
| /* AIN route to related AMUX */ |
| amux_offset = mpp_num % 4; |
| val = (amux_offset << Q_REG_AIN_ROUTE_SHIFT); |
| REG_WRITE((PMI8950_SID * SID_REG_BASE + MPP_REG_BASE + |
| mpp_num * MPP_REG_RANGE + Q_REG_AIN_CTL), val); |
| } |