| /* Copyright (c) 2012-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 <string.h> |
| #include <pm8x41_hw.h> |
| #include <pm8x41.h> |
| #include <platform/timer.h> |
| |
| struct pm8x41_ldo ldo_data[] = { |
| LDO("LDO2", NLDO_TYPE, 0x14100, LDO_RANGE_CTRL, LDO_STEP_CTRL, LDO_EN_CTL_REG), |
| LDO("LDO12", PLDO_TYPE, 0x14B00, LDO_RANGE_CTRL, LDO_STEP_CTRL, LDO_EN_CTL_REG), |
| LDO("LDO22", PLDO_TYPE, 0x15500, LDO_RANGE_CTRL, LDO_STEP_CTRL, LDO_EN_CTL_REG), |
| }; |
| |
| /* SPMI helper functions */ |
| uint8_t pm8x41_reg_read(uint32_t addr) |
| { |
| uint8_t val = 0; |
| struct pmic_arb_cmd cmd; |
| struct pmic_arb_param param; |
| |
| cmd.address = PERIPH_ID(addr); |
| cmd.offset = REG_OFFSET(addr); |
| cmd.slave_id = SLAVE_ID(addr); |
| cmd.priority = 0; |
| |
| param.buffer = &val; |
| param.size = 1; |
| |
| pmic_arb_read_cmd(&cmd, ¶m); |
| |
| return val; |
| } |
| |
| void pm8x41_reg_write(uint32_t addr, uint8_t val) |
| { |
| struct pmic_arb_cmd cmd; |
| struct pmic_arb_param param; |
| |
| cmd.address = PERIPH_ID(addr); |
| cmd.offset = REG_OFFSET(addr); |
| cmd.slave_id = SLAVE_ID(addr); |
| cmd.priority = 0; |
| |
| param.buffer = &val; |
| param.size = 1; |
| |
| pmic_arb_write_cmd(&cmd, ¶m); |
| } |
| |
| /* Exported functions */ |
| |
| /* Set the boot done flag */ |
| void pm8x41_set_boot_done() |
| { |
| uint8_t val; |
| |
| val = REG_READ(SMBB_MISC_BOOT_DONE); |
| val |= BIT(BOOT_DONE_BIT); |
| REG_WRITE(SMBB_MISC_BOOT_DONE, val); |
| } |
| |
| /* Configure GPIO */ |
| int pm8x41_gpio_config(uint8_t gpio, struct pm8x41_gpio *config) |
| { |
| uint8_t val; |
| uint32_t gpio_base = GPIO_N_PERIPHERAL_BASE(gpio); |
| |
| /* Disable the GPIO */ |
| val = REG_READ(gpio_base + GPIO_EN_CTL); |
| val &= ~BIT(PERPH_EN_BIT); |
| REG_WRITE(gpio_base + GPIO_EN_CTL, val); |
| |
| /* Select the mode */ |
| val = config->function | (config->direction << 4); |
| REG_WRITE(gpio_base + GPIO_MODE_CTL, val); |
| |
| /* Set the right pull */ |
| val = config->pull; |
| REG_WRITE(gpio_base + GPIO_DIG_PULL_CTL, val); |
| |
| /* Select the VIN */ |
| val = config->vin_sel; |
| REG_WRITE(gpio_base + GPIO_DIG_VIN_CTL, val); |
| |
| if (config->direction == PM_GPIO_DIR_OUT) { |
| /* Set the right dig out control */ |
| val = config->out_strength | (config->output_buffer << 4); |
| REG_WRITE(gpio_base + GPIO_DIG_OUT_CTL, val); |
| } |
| |
| /* Enable the GPIO */ |
| val = REG_READ(gpio_base + GPIO_EN_CTL); |
| val |= BIT(PERPH_EN_BIT); |
| REG_WRITE(gpio_base + GPIO_EN_CTL, val); |
| |
| return 0; |
| } |
| |
| /* Reads the status of requested gpio */ |
| int pm8x41_gpio_get(uint8_t gpio, uint8_t *status) |
| { |
| uint32_t gpio_base = GPIO_N_PERIPHERAL_BASE(gpio); |
| |
| *status = REG_READ(gpio_base + GPIO_STATUS); |
| |
| /* Return the value of the GPIO pin */ |
| *status &= BIT(GPIO_STATUS_VAL_BIT); |
| |
| dprintf(SPEW, "GPIO %d status is %d\n", gpio, *status); |
| |
| return 0; |
| } |
| |
| /* Write the output value of the requested gpio */ |
| int pm8x41_gpio_set(uint8_t gpio, uint8_t value) |
| { |
| uint32_t gpio_base = GPIO_N_PERIPHERAL_BASE(gpio); |
| uint8_t val; |
| |
| /* Set the output value of the gpio */ |
| val = REG_READ(gpio_base + GPIO_MODE_CTL); |
| val = (val & ~PM_GPIO_OUTPUT_MASK) | value; |
| REG_WRITE(gpio_base + GPIO_MODE_CTL, val); |
| |
| return 0; |
| } |
| |
| /* Prepare PON RESIN S2 reset */ |
| void pm8x41_vol_down_key_prepare() |
| { |
| uint8_t val; |
| |
| /* disable s2 reset */ |
| REG_WRITE(PON_RESIN_N_RESET_S2_CTL, 0x0); |
| |
| /* Delay needed for disable to kick in. */ |
| udelay(300); |
| |
| /* configure s1 timer to 0 */ |
| REG_WRITE(PON_RESIN_N_RESET_S1_TIMER, 0x0); |
| |
| /* configure s2 timer to 2s */ |
| REG_WRITE(PON_RESIN_N_RESET_S2_TIMER, PON_RESIN_N_RESET_S2_TIMER_MAX_VALUE); |
| |
| /* configure reset type */ |
| REG_WRITE(PON_RESIN_N_RESET_S2_CTL, S2_RESET_TYPE_WARM); |
| |
| val = REG_READ(PON_RESIN_N_RESET_S2_CTL); |
| |
| /* enable s2 reset */ |
| val |= BIT(S2_RESET_EN_BIT); |
| REG_WRITE(PON_RESIN_N_RESET_S2_CTL, val); |
| } |
| |
| /* Volume_Down key detect cleanup */ |
| void pm8x41_vol_down_key_done() |
| { |
| /* disable s2 reset */ |
| REG_WRITE(PON_RESIN_N_RESET_S2_CTL, 0x0); |
| |
| /* Delay needed for disable to kick in. */ |
| udelay(300); |
| } |
| |
| /* Volume_Down key status */ |
| int pm8x41_vol_down_key_status() |
| { |
| uint8_t rt_sts = 0; |
| |
| /* Enable S2 reset so we can detect the volume down key press */ |
| pm8x41_vol_down_key_prepare(); |
| |
| /* Delay before interrupt triggering. |
| * See PON_DEBOUNCE_CTL reg. |
| */ |
| mdelay(100); |
| |
| rt_sts = REG_READ(PON_INT_RT_STS); |
| |
| /* Must disable S2 reset otherwise PMIC will reset if key |
| * is held longer than S2 timer. |
| */ |
| pm8x41_vol_down_key_done(); |
| |
| return (rt_sts & BIT(RESIN_BARK_INT_BIT)); |
| } |
| |
| void pm8x41_reset_configure(uint8_t reset_type) |
| { |
| uint8_t val; |
| |
| /* disable PS_HOLD_RESET */ |
| REG_WRITE(PON_PS_HOLD_RESET_CTL, 0x0); |
| |
| /* Delay needed for disable to kick in. */ |
| udelay(300); |
| |
| /* configure reset type */ |
| REG_WRITE(PON_PS_HOLD_RESET_CTL, reset_type); |
| |
| val = REG_READ(PON_PS_HOLD_RESET_CTL); |
| |
| /* enable PS_HOLD_RESET */ |
| val |= BIT(S2_RESET_EN_BIT); |
| REG_WRITE(PON_PS_HOLD_RESET_CTL, val); |
| } |
| |
| static struct pm8x41_ldo *ldo_get(const char *ldo_name) |
| { |
| uint8_t i; |
| struct pm8x41_ldo *ldo = NULL; |
| |
| for (i = 0; i < ARRAY_SIZE(ldo_data); i++) { |
| ldo = &ldo_data[i]; |
| if (!strncmp(ldo->name, ldo_name, strlen(ldo_name))) |
| break; |
| } |
| return ldo; |
| } |
| |
| /* |
| * LDO set voltage, takes ldo name & voltage in UV as input |
| */ |
| int pm8x41_ldo_set_voltage(const char *name, uint32_t voltage) |
| { |
| uint32_t range = 0; |
| uint32_t step = 0; |
| uint32_t mult = 0; |
| uint32_t val = 0; |
| uint32_t vmin = 0; |
| struct pm8x41_ldo *ldo; |
| |
| ldo = ldo_get(name); |
| if (!ldo) { |
| dprintf(CRITICAL, "LDO requsted is not supported: %s\n", name); |
| return 1; |
| } |
| |
| /* Program Normal power mode */ |
| val = 0x0; |
| val = (1 << LDO_NORMAL_PWR_BIT); |
| REG_WRITE((ldo->base + LDO_POWER_MODE), val); |
| |
| /* |
| * Select range, step & vmin based on input voltage & type of LDO |
| * LDO can operate in low, mid, high power mode |
| */ |
| if (ldo->type == PLDO_TYPE) { |
| if (voltage < PLDO_UV_MIN) { |
| range = 2; |
| step = PLDO_UV_STEP_LOW; |
| vmin = PLDO_UV_VMIN_LOW; |
| } else if (voltage < PDLO_UV_MID) { |
| range = 3; |
| step = PLDO_UV_STEP_MID; |
| vmin = PLDO_UV_VMIN_MID; |
| } else { |
| range = 4; |
| step = PLDO_UV_STEP_HIGH; |
| vmin = PLDO_UV_VMIN_HIGH; |
| } |
| } else { |
| range = 2; |
| step = NLDO_UV_STEP; |
| vmin = NLDO_UV_VMIN_LOW; |
| } |
| |
| mult = (voltage - vmin) / step; |
| |
| /* Set Range in voltage ctrl register */ |
| val = 0x0; |
| val = range << LDO_RANGE_SEL_BIT; |
| REG_WRITE((ldo->base + ldo->range_reg), val); |
| |
| /* Set multiplier in voltage ctrl register */ |
| val = 0x0; |
| val = mult << LDO_VSET_SEL_BIT; |
| REG_WRITE((ldo->base + ldo->step_reg), val); |
| |
| return 0; |
| } |
| |
| /* |
| * Enable or Disable LDO |
| */ |
| int pm8x41_ldo_control(const char *name, uint8_t enable) |
| { |
| uint32_t val = 0; |
| struct pm8x41_ldo *ldo; |
| |
| ldo = ldo_get(name); |
| if (!ldo) { |
| dprintf(CRITICAL, "Requested LDO is not supported : %s\n", name); |
| return 1; |
| } |
| |
| /* Enable LDO */ |
| if (enable) |
| val = (1 << LDO_VREG_ENABLE_BIT); |
| else |
| val = (0 << LDO_VREG_ENABLE_BIT); |
| |
| REG_WRITE((ldo->base + ldo->enable_reg), val); |
| |
| return 0; |
| } |