diff options
Diffstat (limited to 'drivers/soc/qcom/msm-spm.c')
-rw-r--r-- | drivers/soc/qcom/msm-spm.c | 715 |
1 files changed, 715 insertions, 0 deletions
diff --git a/drivers/soc/qcom/msm-spm.c b/drivers/soc/qcom/msm-spm.c new file mode 100644 index 000000000000..508aa77c7e42 --- /dev/null +++ b/drivers/soc/qcom/msm-spm.c @@ -0,0 +1,715 @@ +/* Copyright (c) 2011-2016, 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include "spm_driver.h" + +#define MSM_SPM_PMIC_STATE_IDLE 0 + +enum { + MSM_SPM_DEBUG_SHADOW = 1U << 0, + MSM_SPM_DEBUG_VCTL = 1U << 1, +}; + +static int msm_spm_debug_mask; +module_param_named( + debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +struct saw2_data { + const char *ver_name; + uint32_t major; + uint32_t minor; + uint32_t *spm_reg_offset_ptr; +}; + +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = { + [MSM_SPM_REG_SAW_SECURE] = 0x00, + [MSM_SPM_REG_SAW_ID] = 0x04, + [MSM_SPM_REG_SAW_CFG] = 0x08, + [MSM_SPM_REG_SAW_SPM_STS] = 0x0C, + [MSM_SPM_REG_SAW_AVS_STS] = 0x10, + [MSM_SPM_REG_SAW_PMIC_STS] = 0x14, + [MSM_SPM_REG_SAW_RST] = 0x18, + [MSM_SPM_REG_SAW_VCTL] = 0x1C, + [MSM_SPM_REG_SAW_AVS_CTL] = 0x20, + [MSM_SPM_REG_SAW_AVS_LIMIT] = 0x24, + [MSM_SPM_REG_SAW_AVS_DLY] = 0x28, + [MSM_SPM_REG_SAW_AVS_HYSTERESIS] = 0x2C, + [MSM_SPM_REG_SAW_SPM_CTL] = 0x30, + [MSM_SPM_REG_SAW_SPM_DLY] = 0x34, + [MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40, + [MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44, + [MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48, + [MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C, + [MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50, + [MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54, + [MSM_SPM_REG_SAW_PMIC_DATA_6] = 0x58, + [MSM_SPM_REG_SAW_PMIC_DATA_7] = 0x5C, + [MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x80, + [MSM_SPM_REG_SAW_VERSION] = 0xFD0, +}; + +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = { + [MSM_SPM_REG_SAW_SECURE] = 0x00, + [MSM_SPM_REG_SAW_ID] = 0x04, + [MSM_SPM_REG_SAW_CFG] = 0x08, + [MSM_SPM_REG_SAW_SPM_STS] = 0x0C, + [MSM_SPM_REG_SAW_AVS_STS] = 0x10, + [MSM_SPM_REG_SAW_PMIC_STS] = 0x14, + [MSM_SPM_REG_SAW_RST] = 0x18, + [MSM_SPM_REG_SAW_VCTL] = 0x1C, + [MSM_SPM_REG_SAW_AVS_CTL] = 0x20, + [MSM_SPM_REG_SAW_AVS_LIMIT] = 0x24, + [MSM_SPM_REG_SAW_AVS_DLY] = 0x28, + [MSM_SPM_REG_SAW_AVS_HYSTERESIS] = 0x2C, + [MSM_SPM_REG_SAW_SPM_CTL] = 0x30, + [MSM_SPM_REG_SAW_SPM_DLY] = 0x34, + [MSM_SPM_REG_SAW_STS2] = 0x38, + [MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40, + [MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44, + [MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48, + [MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C, + [MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50, + [MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54, + [MSM_SPM_REG_SAW_PMIC_DATA_6] = 0x58, + [MSM_SPM_REG_SAW_PMIC_DATA_7] = 0x5C, + [MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x400, + [MSM_SPM_REG_SAW_VERSION] = 0xFD0, +}; + +static uint32_t msm_spm_reg_offsets_saw2_v4_1[MSM_SPM_REG_NR] = { + [MSM_SPM_REG_SAW_SECURE] = 0xC00, + [MSM_SPM_REG_SAW_ID] = 0xC04, + [MSM_SPM_REG_SAW_STS2] = 0xC10, + [MSM_SPM_REG_SAW_SPM_STS] = 0xC0C, + [MSM_SPM_REG_SAW_AVS_STS] = 0xC14, + [MSM_SPM_REG_SAW_PMIC_STS] = 0xC18, + [MSM_SPM_REG_SAW_RST] = 0xC1C, + [MSM_SPM_REG_SAW_VCTL] = 0x900, + [MSM_SPM_REG_SAW_AVS_CTL] = 0x904, + [MSM_SPM_REG_SAW_AVS_LIMIT] = 0x908, + [MSM_SPM_REG_SAW_AVS_DLY] = 0x90C, + [MSM_SPM_REG_SAW_SPM_CTL] = 0x0, + [MSM_SPM_REG_SAW_SPM_DLY] = 0x4, + [MSM_SPM_REG_SAW_CFG] = 0x0C, + [MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40, + [MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44, + [MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48, + [MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C, + [MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50, + [MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54, + [MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x400, + [MSM_SPM_REG_SAW_VERSION] = 0xFD0, +}; + +static struct saw2_data saw2_info[] = { + [0] = { + "SAW_v2.1", + 0x2, + 0x1, + msm_spm_reg_offsets_saw2_v2_1, + }, + [1] = { + "SAW_v2.3", + 0x3, + 0x0, + msm_spm_reg_offsets_saw2_v3_0, + }, + [2] = { + "SAW_v3.0", + 0x1, + 0x0, + msm_spm_reg_offsets_saw2_v3_0, + }, + [3] = { + "SAW_v4.0", + 0x4, + 0x1, + msm_spm_reg_offsets_saw2_v4_1, + }, +}; + +static uint32_t num_pmic_data; + +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + BUG_ON(!dev); + + BUG_ON(!dev->reg_shadow); + + __raw_writel(dev->reg_shadow[reg_index], + dev->reg_base_addr + dev->reg_offsets[reg_index]); +} + +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + dev->reg_shadow[reg_index] = + __raw_readl(dev->reg_base_addr + + dev->reg_offsets[reg_index]); +} + +static inline uint32_t msm_spm_drv_get_num_spm_entry( + struct msm_spm_driver_data *dev) +{ + BUG_ON(!dev); + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID); + return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 24) & 0xFF; +} + +static inline void msm_spm_drv_set_start_addr( + struct msm_spm_driver_data *dev, uint32_t ctl) +{ + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] = ctl; +} + +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID); + return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 2) & 0x1; +} + +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, + uint32_t vlevel) +{ + unsigned int pmic_data = 0; + + /** + * VCTL_PORT has to be 0, for PMIC_STS register to be updated. + * Ensure that vctl_port is always set to 0. + */ + WARN_ON(dev->vctl_port); + + pmic_data |= vlevel; + pmic_data |= (dev->vctl_port & 0x7) << 16; + + dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= pmic_data; + + dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_DATA_3] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_DATA_3] |= pmic_data; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_PMIC_DATA_3); +} + +static inline uint32_t msm_spm_drv_get_num_pmic_data( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID); + mb(); + return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 4) & 0x7; +} + +static inline uint32_t msm_spm_drv_get_sts_pmic_state( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS); + return (dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] >> 16) & + 0x03; +} + +uint32_t msm_spm_drv_get_sts_curr_pmic_data( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS); + return dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] & 0x300FF; +} + +static inline void msm_spm_drv_get_saw2_ver(struct msm_spm_driver_data *dev, + uint32_t *major, uint32_t *minor) +{ + uint32_t val = 0; + + dev->reg_shadow[MSM_SPM_REG_SAW_VERSION] = + __raw_readl(dev->reg_base_addr + dev->ver_reg); + + val = dev->reg_shadow[MSM_SPM_REG_SAW_VERSION]; + + *major = (val >> 28) & 0xF; + *minor = (val >> 16) & 0xFFF; +} + +inline int msm_spm_drv_set_spm_enable( + struct msm_spm_driver_data *dev, bool enable) +{ + uint32_t value = enable ? 0x01 : 0x00; + + if (!dev) + return -EINVAL; + + if ((dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] & 0x01) ^ value) { + + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] &= ~0x1; + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= value; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL); + wmb(); + } + return 0; +} + +int msm_spm_drv_get_avs_enable(struct msm_spm_driver_data *dev) +{ + if (!dev) + return -EINVAL; + + return dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & 0x01; +} + +int msm_spm_drv_set_avs_enable(struct msm_spm_driver_data *dev, + bool enable) +{ + uint32_t value = enable ? 0x1 : 0x0; + + if (!dev) + return -EINVAL; + + if ((dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & 0x1) ^ value) { + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~0x1; + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= value; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); + } + + return 0; +} + +int msm_spm_drv_set_avs_limit(struct msm_spm_driver_data *dev, + uint32_t min_lvl, uint32_t max_lvl) +{ + uint32_t value = (max_lvl & 0xff) << 16 | (min_lvl & 0xff); + + if (!dev) + return -EINVAL; + + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_LIMIT] = value; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_LIMIT); + + return 0; +} + +static int msm_spm_drv_avs_irq_mask(enum msm_spm_avs_irq irq) +{ + switch (irq) { + case MSM_SPM_AVS_IRQ_MIN: + return BIT(1); + case MSM_SPM_AVS_IRQ_MAX: + return BIT(2); + default: + return -EINVAL; + } +} + +int msm_spm_drv_set_avs_irq_enable(struct msm_spm_driver_data *dev, + enum msm_spm_avs_irq irq, bool enable) +{ + int mask = msm_spm_drv_avs_irq_mask(irq); + uint32_t value; + + if (!dev) + return -EINVAL; + else if (mask < 0) + return mask; + + value = enable ? mask : 0; + + if ((dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & mask) ^ value) { + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~mask; + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= value; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); + } + + return 0; +} + +int msm_spm_drv_avs_clear_irq(struct msm_spm_driver_data *dev, + enum msm_spm_avs_irq irq) +{ + int mask = msm_spm_drv_avs_irq_mask(irq); + + if (!dev) + return -EINVAL; + else if (mask < 0) + return mask; + + if (dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & mask) { + /* + * The interrupt status is cleared by disabling and then + * re-enabling the interrupt. + */ + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~mask; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= mask; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); + } + + return 0; +} + +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev) +{ + int i; + int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + if (!dev) { + __WARN(); + return; + } + + for (i = 0; i < num_spm_entry; i++) { + __raw_writel(dev->reg_seq_entry_shadow[i], + dev->reg_base_addr + + dev->reg_offsets[MSM_SPM_REG_SAW_SEQ_ENTRY] + + 4 * i); + } + mb(); +} + +void dump_regs(struct msm_spm_driver_data *dev, int cpu) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_STS); + mb(); + pr_err("CPU%d: spm register MSM_SPM_REG_SAW_SPM_STS: 0x%x\n", cpu, + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_STS]); + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL); + mb(); + pr_err("CPU%d: spm register MSM_SPM_REG_SAW_SPM_CTL: 0x%x\n", cpu, + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL]); +} + +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, + uint8_t *cmd, uint32_t *offset) +{ + uint32_t cmd_w; + uint32_t offset_w = *offset / 4; + uint8_t last_cmd; + + if (!cmd) + return -EINVAL; + + while (1) { + int i; + cmd_w = 0; + last_cmd = 0; + cmd_w = dev->reg_seq_entry_shadow[offset_w]; + + for (i = (*offset % 4); i < 4; i++) { + last_cmd = *(cmd++); + cmd_w |= last_cmd << (i * 8); + (*offset)++; + if (last_cmd == 0x0f) + break; + } + + dev->reg_seq_entry_shadow[offset_w++] = cmd_w; + if (last_cmd == 0x0f) + break; + } + + return 0; +} + +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t ctl) +{ + + /* SPM is configured to reset start address to zero after end of Program + */ + if (!dev) + return -EINVAL; + + msm_spm_drv_set_start_addr(dev, ctl); + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL); + wmb(); + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { + int i; + for (i = 0; i < MSM_SPM_REG_NR; i++) + pr_info("%s: reg %02x = 0x%08x\n", __func__, + dev->reg_offsets[i], dev->reg_shadow[i]); + } + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_STS); + + return 0; +} + +uint32_t msm_spm_drv_get_vdd(struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS); + return dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] & 0xFF; +} + +#ifdef CONFIG_MSM_AVS_HW +static bool msm_spm_drv_is_avs_enabled(struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); + return dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & BIT(0); +} + +static void msm_spm_drv_disable_avs(struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~BIT(27); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); +} + +static void msm_spm_drv_enable_avs(struct msm_spm_driver_data *dev) +{ + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= BIT(27); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); +} + +static void msm_spm_drv_set_avs_vlevel(struct msm_spm_driver_data *dev, + unsigned int vlevel) +{ + vlevel &= 0x3f; + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~0x7efc00; + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= ((vlevel - 4) << 10); + dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= (vlevel << 17); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL); +} + +#else +static bool msm_spm_drv_is_avs_enabled(struct msm_spm_driver_data *dev) +{ + return false; +} + +static void msm_spm_drv_disable_avs(struct msm_spm_driver_data *dev) { } + +static void msm_spm_drv_enable_avs(struct msm_spm_driver_data *dev) { } + +static void msm_spm_drv_set_avs_vlevel(struct msm_spm_driver_data *dev, + unsigned int vlevel) { } +#endif + +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel) +{ + uint32_t timeout_us, new_level; + bool avs_enabled; + + if (!dev) + return -EINVAL; + + avs_enabled = msm_spm_drv_is_avs_enabled(dev); + + if (!msm_spm_pmic_arb_present(dev)) + return -ENOSYS; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: requesting vlevel %#x\n", __func__, vlevel); + + if (avs_enabled) + msm_spm_drv_disable_avs(dev); + + /* Kick the state machine back to idle */ + dev->reg_shadow[MSM_SPM_REG_SAW_RST] = 1; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_RST); + + msm_spm_drv_set_vctl2(dev, vlevel); + + timeout_us = dev->vctl_timeout_us; + /* Confirm the voltage we set was what hardware sent */ + do { + udelay(1); + new_level = msm_spm_drv_get_sts_curr_pmic_data(dev); + /* FSM is idle */ + if (((new_level & 0x30000) == 0) && + ((new_level & 0xFF) == vlevel)) + break; + } while (--timeout_us); + if (!timeout_us) { + pr_info("Wrong level %#x\n", new_level); + goto set_vdd_bail; + } + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: done, remaining timeout %u us\n", + __func__, timeout_us); + + /* Set AVS min/max */ + if (avs_enabled) { + msm_spm_drv_set_avs_vlevel(dev, vlevel); + msm_spm_drv_enable_avs(dev); + } + + return 0; + +set_vdd_bail: + if (avs_enabled) + msm_spm_drv_enable_avs(dev); + + pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n", + __func__, vlevel, timeout_us, new_level); + return -EIO; +} + +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev, + enum msm_spm_pmic_port port) +{ + int index = -1; + + switch (port) { + case MSM_SPM_PMIC_VCTL_PORT: + index = dev->vctl_port; + break; + case MSM_SPM_PMIC_PHASE_PORT: + index = dev->phase_port; + break; + case MSM_SPM_PMIC_PFM_PORT: + index = dev->pfm_port; + break; + default: + break; + } + + return index; +} + +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, + enum msm_spm_pmic_port port, unsigned int data) +{ + unsigned int pmic_data = 0; + unsigned int timeout_us = 0; + int index = 0; + + if (!msm_spm_pmic_arb_present(dev)) + return -ENOSYS; + + index = msm_spm_drv_get_pmic_port(dev, port); + if (index < 0) + return -ENODEV; + + pmic_data |= data & 0xFF; + pmic_data |= (index & 0x7) << 16; + + dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= pmic_data; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL); + mb(); + + timeout_us = dev->vctl_timeout_us; + /** + * Confirm the pmic data set was what hardware sent by + * checking the PMIC FSM state. + * We cannot use the sts_pmic_data and check it against + * the value like we do fot set_vdd, since the PMIC_STS + * is only updated for SAW_VCTL sent with port index 0. + */ + do { + if (msm_spm_drv_get_sts_pmic_state(dev) == + MSM_SPM_PMIC_STATE_IDLE) + break; + udelay(1); + } while (--timeout_us); + + if (!timeout_us) { + pr_err("%s: failed, remaining timeout %u us, data %d\n", + __func__, timeout_us, data); + return -EIO; + } + + return 0; +} + +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev, bool seq_write) +{ + int i; + + if (seq_write) + msm_spm_drv_flush_seq_entry(dev); + + for (i = 0; i < MSM_SPM_REG_SAW_PMIC_DATA_0 + num_pmic_data; i++) + msm_spm_drv_load_shadow(dev, i); + + for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++) + msm_spm_drv_load_shadow(dev, i); +} + +int msm_spm_drv_reg_init(struct msm_spm_driver_data *dev, + struct msm_spm_platform_data *data) +{ + int i; + bool found = false; + + dev->ver_reg = data->ver_reg; + dev->reg_base_addr = data->reg_base_addr; + msm_spm_drv_get_saw2_ver(dev, &dev->major, &dev->minor); + for (i = 0; i < ARRAY_SIZE(saw2_info); i++) + if (dev->major == saw2_info[i].major && + dev->minor == saw2_info[i].minor) { + pr_debug("%s: Version found\n", + saw2_info[i].ver_name); + dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr; + found = true; + break; + } + + if (!found) { + pr_err("%s: No SAW version found\n", __func__); + BUG_ON(!found); + } + return 0; +} + +void msm_spm_drv_upd_reg_shadow(struct msm_spm_driver_data *dev, int id, + int val) +{ + dev->reg_shadow[id] = val; + msm_spm_drv_flush_shadow(dev, id); + /* Complete the above writes before other accesses */ + mb(); +} + +int msm_spm_drv_init(struct msm_spm_driver_data *dev, + struct msm_spm_platform_data *data) +{ + int num_spm_entry; + + BUG_ON(!dev || !data); + + dev->vctl_port = data->vctl_port; + dev->phase_port = data->phase_port; + dev->pfm_port = data->pfm_port; + dev->reg_base_addr = data->reg_base_addr; + memcpy(dev->reg_shadow, data->reg_init_values, + sizeof(data->reg_init_values)); + + dev->vctl_timeout_us = data->vctl_timeout_us; + + + if (!num_pmic_data) + num_pmic_data = msm_spm_drv_get_num_pmic_data(dev); + + num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + dev->reg_seq_entry_shadow = + kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry, + GFP_KERNEL); + + if (!dev->reg_seq_entry_shadow) + return -ENOMEM; + + return 0; +} |