| /* Copyright (c) 2011-2017, 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, 0664 |
| ); |
| |
| 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) |
| { |
| if (!dev || !dev->reg_shadow) |
| return; |
| |
| __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) |
| { |
| if (!dev) |
| return; |
| |
| 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. |
| */ |
| if (dev->vctl_port) { |
| WARN(); |
| return; |
| } |
| |
| 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(); /* Ensure we flush */ |
| 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(); /* Ensure we flush */ |
| } |
| 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(); /* Ensure we flush */ |
| } |
| |
| void dump_regs(struct msm_spm_driver_data *dev, int cpu) |
| { |
| msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_STS); |
| mb(); /* Ensure we flush */ |
| 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(); /* Ensure we flush */ |
| 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(); /* Ensure we flush */ |
| |
| 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 -ENODEV; |
| |
| 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 -ENODEV; |
| |
| 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(); /* Ensure we flush */ |
| |
| 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__); |
| WARN_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; |
| |
| if (!dev || !data) |
| return -ENODEV; |
| |
| 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; |
| } |