/* Copyright (c) 2010-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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /****************************************************************************** * rpm_platform data from legacy devices-ipq806x.c *****************************************************************************/ struct msm_rpm_platform_data ipq806x_rpm_data = { .reg_base_addrs = { [MSM_RPM_PAGE_STATUS] = (void*)0x0, [MSM_RPM_PAGE_CTRL] = (void*)0x400, [MSM_RPM_PAGE_REQ] = (void*)0x600, [MSM_RPM_PAGE_ACK] = (void*)0xa00, }, .ipc_rpm_reg = (void*)0x008, .ipc_rpm_val = 4, .target_id = { MSM_RPM_MAP(IPQ806X, NOTIFICATION_CONFIGURED_0, NOTIFICATION, 4, "notification_configured"), MSM_RPM_MAP(IPQ806X, NOTIFICATION_REGISTERED_0, NOTIFICATION, 4, "notification_registered"), MSM_RPM_MAP(IPQ806X, INVALIDATE_0, INVALIDATE, 8, "invalidate"), MSM_RPM_MAP(IPQ806X, TRIGGER_TIMED_TO, TRIGGER_TIMED, 1, "trigger_timed_to"), MSM_RPM_MAP(IPQ806X, TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1, "trigger_timed_sclk_count"), MSM_RPM_MAP(IPQ806X, RPM_CTL, RPM_CTL, 1, "rpm_ctl"), MSM_RPM_MAP(IPQ806X, CXO_CLK, CXO_CLK, 1, "cxo_clk"), MSM_RPM_MAP(IPQ806X, PXO_CLK, PXO_CLK, 1, "pxo_clk"), MSM_RPM_MAP(IPQ806X, APPS_FABRIC_CLK, APPS_FABRIC_CLK, 1, "apps_fabric_clk"), MSM_RPM_MAP(IPQ806X, SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1, "system_fabric_clk"), MSM_RPM_MAP(IPQ806X, DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1, "daytona_fabric_clk"), MSM_RPM_MAP(IPQ806X, SFPB_CLK, SFPB_CLK, 1, "sfpb_clk"), MSM_RPM_MAP(IPQ806X, CFPB_CLK, CFPB_CLK, 1, "cfpb_clk"), MSM_RPM_MAP(IPQ806X, EBI1_CLK, EBI1_CLK, 1, "ebi1_clk"), MSM_RPM_MAP(IPQ806X, APPS_FABRIC_CFG_HALT_0, APPS_FABRIC_CFG_HALT, 2, "apps_fabric_cfg_halt"), MSM_RPM_MAP(IPQ806X, APPS_FABRIC_CFG_CLKMOD_0, APPS_FABRIC_CFG_CLKMOD, 3, "apps_fabric_cfg_clkmod" ), MSM_RPM_MAP(IPQ806X, APPS_FABRIC_CFG_IOCTL, APPS_FABRIC_CFG_IOCTL, 1, "apps_fabric_cfg_ioctl" ), MSM_RPM_MAP(IPQ806X, APPS_FABRIC_ARB_0, APPS_FABRIC_ARB, 12, "apps_fabric_arb"), MSM_RPM_MAP(IPQ806X, SYS_FABRIC_CFG_HALT_0, SYS_FABRIC_CFG_HALT, 2, "sys_fabric_cfg_halt" ), MSM_RPM_MAP(IPQ806X, SYS_FABRIC_CFG_CLKMOD_0, SYS_FABRIC_CFG_CLKMOD, 3, "sys_fabric_cfg_clkmod" ), MSM_RPM_MAP(IPQ806X, SYS_FABRIC_CFG_IOCTL, SYS_FABRIC_CFG_IOCTL, 1, "sys_fabric_cfg_ioctl" ), MSM_RPM_MAP(IPQ806X, SYSTEM_FABRIC_ARB_0, SYSTEM_FABRIC_ARB, 30, "system_fabric_arb"), MSM_RPM_MAP(IPQ806X, MMSS_FABRIC_CFG_HALT_0, MMSS_FABRIC_CFG_HALT, 2, "mmss_fabric_cfg_halt" ), MSM_RPM_MAP(IPQ806X, MMSS_FABRIC_CFG_CLKMOD_0, MMSS_FABRIC_CFG_CLKMOD, 3, "mmss_fabric_cfg_clkmod" ), MSM_RPM_MAP(IPQ806X, MMSS_FABRIC_CFG_IOCTL, MMSS_FABRIC_CFG_IOCTL, 1, "mmss_fabric_cfg_ioctl" ), MSM_RPM_MAP(IPQ806X, MM_FABRIC_ARB_0, MM_FABRIC_ARB, 2, "mm_fabric_arb"), MSM_RPM_MAP(IPQ806X, NCP_0, NCP, 2, "ncp"), MSM_RPM_MAP(IPQ806X, CXO_BUFFERS, CXO_BUFFERS, 1, "cxo_buffers"), MSM_RPM_MAP(IPQ806X, USB_OTG_SWITCH, USB_OTG_SWITCH, 1, "usb_otg_switch"), MSM_RPM_MAP(IPQ806X, HDMI_SWITCH, HDMI_SWITCH, 1, "hdmi_switch"), MSM_RPM_MAP(IPQ806X, DDR_DMM_0, DDR_DMM, 2, "ddr_dmm"), MSM_RPM_MAP(IPQ806X, QDSS_CLK, QDSS_CLK, 1, "qdss_clk"), MSM_RPM_MAP(IPQ806X, VDDMIN_GPIO, VDDMIN_GPIO, 1, "vddmin_gpio"), MSM_RPM_MAP(IPQ806X, SMB208_S1a_0, SMB208_S1a, 2, "smb208_s1a"), MSM_RPM_MAP(IPQ806X, SMB208_S1b_0, SMB208_S1b, 2, "smb208_s1b"), MSM_RPM_MAP(IPQ806X, SMB208_S2a_0, SMB208_S2a, 2, "smb208_s2a"), MSM_RPM_MAP(IPQ806X, SMB208_S2b_0, SMB208_S2b, 2, "smb208_s2b"), MSM_RPM_MAP(IPQ806X, DDR_SELF_REFRESH, DDR_SELF_REFRESH, 1, "ddr_self_refresh"), MSM_RPM_MAP(IPQ806X, ENTER_IDLE, ENTER_IDLE, 1, "enter_idle"), MSM_RPM_MAP(IPQ806X, NSS_FABRIC_0_CLK, NSS_FABRIC_0_CLK, 1, "nss_fabric_0_clk" ), MSM_RPM_MAP(IPQ806X, NSS_FABRIC_1_CLK, NSS_FABRIC_1_CLK, 1, "nss_fabric_1_clk" ), }, .target_status = { MSM_RPM_STATUS_ID_MAP(IPQ806X, VERSION_MAJOR), MSM_RPM_STATUS_ID_MAP(IPQ806X, VERSION_MINOR), MSM_RPM_STATUS_ID_MAP(IPQ806X, VERSION_BUILD), MSM_RPM_STATUS_ID_MAP(IPQ806X, SUPPORTED_RESOURCES_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, SUPPORTED_RESOURCES_1), MSM_RPM_STATUS_ID_MAP(IPQ806X, SUPPORTED_RESOURCES_2), MSM_RPM_STATUS_ID_MAP(IPQ806X, RESERVED_SUPPORTED_RESOURCES_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, SEQUENCE), MSM_RPM_STATUS_ID_MAP(IPQ806X, RPM_CTL), MSM_RPM_STATUS_ID_MAP(IPQ806X, CXO_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, PXO_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, APPS_FABRIC_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, SYSTEM_FABRIC_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, DAYTONA_FABRIC_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, SFPB_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, CFPB_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, EBI1_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, APPS_FABRIC_CFG_HALT), MSM_RPM_STATUS_ID_MAP(IPQ806X, APPS_FABRIC_CFG_CLKMOD), MSM_RPM_STATUS_ID_MAP(IPQ806X, APPS_FABRIC_CFG_IOCTL), MSM_RPM_STATUS_ID_MAP(IPQ806X, APPS_FABRIC_ARB), MSM_RPM_STATUS_ID_MAP(IPQ806X, SYS_FABRIC_CFG_HALT), MSM_RPM_STATUS_ID_MAP(IPQ806X, SYS_FABRIC_CFG_CLKMOD), MSM_RPM_STATUS_ID_MAP(IPQ806X, SYS_FABRIC_CFG_IOCTL), MSM_RPM_STATUS_ID_MAP(IPQ806X, SYSTEM_FABRIC_ARB), MSM_RPM_STATUS_ID_MAP(IPQ806X, MMSS_FABRIC_CFG_HALT), MSM_RPM_STATUS_ID_MAP(IPQ806X, MMSS_FABRIC_CFG_CLKMOD), MSM_RPM_STATUS_ID_MAP(IPQ806X, MMSS_FABRIC_CFG_IOCTL), MSM_RPM_STATUS_ID_MAP(IPQ806X, MM_FABRIC_ARB), MSM_RPM_STATUS_ID_MAP(IPQ806X, NCP_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, NCP_1), MSM_RPM_STATUS_ID_MAP(IPQ806X, CXO_BUFFERS), MSM_RPM_STATUS_ID_MAP(IPQ806X, USB_OTG_SWITCH), MSM_RPM_STATUS_ID_MAP(IPQ806X, HDMI_SWITCH), MSM_RPM_STATUS_ID_MAP(IPQ806X, DDR_DMM_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, DDR_DMM_1), MSM_RPM_STATUS_ID_MAP(IPQ806X, EBI1_CH0_RANGE), MSM_RPM_STATUS_ID_MAP(IPQ806X, EBI1_CH1_RANGE), MSM_RPM_STATUS_ID_MAP(IPQ806X, VDDMIN_GPIO), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S1a_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S1a_1), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S1b_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S1b_1), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S2a_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S2a_1), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S2b_0), MSM_RPM_STATUS_ID_MAP(IPQ806X, SMB208_S2b_1), MSM_RPM_STATUS_ID_MAP(IPQ806X, DDR_SELF_REFRESH), MSM_RPM_STATUS_ID_MAP(IPQ806X, ENTER_IDLE), MSM_RPM_STATUS_ID_MAP(IPQ806X, NSS_FABRIC_0_CLK), MSM_RPM_STATUS_ID_MAP(IPQ806X, NSS_FABRIC_1_CLK), }, .target_ctrl_id = { MSM_RPM_CTRL_MAP(IPQ806X, VERSION_MAJOR), MSM_RPM_CTRL_MAP(IPQ806X, VERSION_MINOR), MSM_RPM_CTRL_MAP(IPQ806X, VERSION_BUILD), MSM_RPM_CTRL_MAP(IPQ806X, REQ_CTX_0), MSM_RPM_CTRL_MAP(IPQ806X, REQ_SEL_0), MSM_RPM_CTRL_MAP(IPQ806X, ACK_CTX_0), MSM_RPM_CTRL_MAP(IPQ806X, ACK_SEL_0), }, .sel_invalidate = MSM_RPM_IPQ806X_SEL_INVALIDATE, .sel_notification = MSM_RPM_IPQ806X_SEL_NOTIFICATION, .sel_last = MSM_RPM_IPQ806X_SEL_LAST, .ver = {3, 0, 0}, }; /****************************************************************************** * Data type and structure definitions *****************************************************************************/ struct msm_rpm_request { struct msm_rpm_iv_pair *req; int count; uint32_t *ctx_mask_ack; uint32_t *sel_masks_ack; struct completion *done; }; struct msm_rpm_notif_config { struct msm_rpm_iv_pair iv[SEL_MASK_SIZE * 2]; }; #define configured_iv(notif_cfg) ((notif_cfg)->iv) #define registered_iv(notif_cfg) ((notif_cfg)->iv + msm_rpm_sel_mask_size) static uint32_t msm_rpm_sel_mask_size; static struct msm_rpm_platform_data msm_rpm_data; static DEFINE_MUTEX(msm_rpm_mutex); static DEFINE_SPINLOCK(msm_rpm_lock); static DEFINE_SPINLOCK(msm_rpm_irq_lock); static struct msm_rpm_request *msm_rpm_request; static struct msm_rpm_request msm_rpm_request_irq_mode; #ifdef NOIRQ_SUPPORT static struct msm_rpm_request msm_rpm_request_poll_mode; #endif struct completion *wake_done; static LIST_HEAD(msm_rpm_notifications); static struct msm_rpm_notif_config msm_rpm_notif_cfgs[MSM_RPM_CTX_SET_COUNT]; static bool msm_rpm_init_notif_done; #ifdef CONFIG_DEBUG_FS extern int rpm_debug_init(struct msm_rpm_platform_data *data); #endif static void rpm_setup_regulators(void); static void rpm_set_regulator_voltages_high(void); /****************************************************************************** * Internal functions *****************************************************************************/ static inline unsigned int target_enum(unsigned int id) { BUG_ON(id >= MSM_RPM_ID_LAST); return msm_rpm_data.target_id[id].id; } static inline unsigned int target_status(unsigned int id) { BUG_ON(id >= MSM_RPM_STATUS_ID_LAST); return msm_rpm_data.target_status[id]; } static inline unsigned int target_ctrl(unsigned int id) { BUG_ON(id >= MSM_RPM_CTRL_LAST); return msm_rpm_data.target_ctrl_id[id]; } static inline uint32_t msm_rpm_read(unsigned int page, unsigned int reg) { return __raw_readl(msm_rpm_data.reg_base_addrs[page] + reg * 4); } uint64_t rpm_debug_filter_low = 0; uint64_t rpm_debug_filter_high = 0; static inline void msm_rpm_write( unsigned int page, unsigned int reg, uint32_t value) { const char *page_to_str[] = { [MSM_RPM_PAGE_STATUS] = "MSM_RPM_PAGE_STATUS", [MSM_RPM_PAGE_CTRL] = "MSM_RPM_PAGE_CTRL", [MSM_RPM_PAGE_REQ] = "MSM_RPM_PAGE_REQ", [MSM_RPM_PAGE_ACK] = "MSM_RPM_PAGE_ACK", [MSM_RPM_PAGE_COUNT] = "MSM_RPM_PAGE_COUNT", }; if (((reg < 64) && ((1 << reg) & rpm_debug_filter_low)) || ((reg >= 64) && ((1 << (reg-64)) & rpm_debug_filter_high))) { pr_err("\e[31;1m%s: page=%s (%d), reg=0x%08x, value=0x%08x\e[m\n", __func__, page > MSM_RPM_PAGE_COUNT ? "(EINVAL)" : page_to_str[page], page, reg, value); } __raw_writel(value, msm_rpm_data.reg_base_addrs[page] + reg * 4); } static inline void msm_rpm_read_contiguous( unsigned int page, unsigned int reg, uint32_t *values, int count) { int i; for (i = 0; i < count; i++) values[i] = msm_rpm_read(page, reg + i); } static inline void msm_rpm_write_contiguous( unsigned int page, unsigned int reg, uint32_t *values, int count) { int i; for (i = 0; i < count; i++) msm_rpm_write(page, reg + i, values[i]); } static inline void msm_rpm_write_contiguous_zeros( unsigned int page, unsigned int reg, int count) { int i; for (i = 0; i < count; i++) msm_rpm_write(page, reg + i, 0); } static inline uint32_t msm_rpm_map_id_to_sel(uint32_t id) { return (id >= MSM_RPM_ID_LAST) ? msm_rpm_data.sel_last + 1 : msm_rpm_data.target_id[id].sel; } /* * Note: the function does not clear the masks before filling them. * * Return value: * 0: success * -EINVAL: invalid id in array */ static int msm_rpm_fill_sel_masks( uint32_t *sel_masks, struct msm_rpm_iv_pair *req, int count) { uint32_t sel; int i; for (i = 0; i < count; i++) { sel = msm_rpm_map_id_to_sel(req[i].id); if (sel > msm_rpm_data.sel_last) { pr_err("%s(): RPM ID %d not defined for target\n", __func__, req[i].id); return -EINVAL; } sel_masks[msm_rpm_get_sel_mask_reg(sel)] |= msm_rpm_get_sel_mask(sel); } return 0; } static inline void msm_rpm_send_req_interrupt(void) { __raw_writel(msm_rpm_data.ipc_rpm_val, msm_rpm_data.ipc_rpm_reg); } static inline void msm_rpm_send_idle_interrupt(void) { __raw_writel(BIT(1), msm_rpm_data.ipc_rpm_reg); } /* * Note: assumes caller has acquired . * * Return value: * 0: request acknowledgement * 1: notification * 2: spurious interrupt */ static int msm_rpm_process_ack_interrupt(void) { uint32_t ctx_mask_ack; uint32_t sel_masks_ack[SEL_MASK_SIZE] = {0}; ctx_mask_ack = msm_rpm_read(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_ACK_CTX_0)); msm_rpm_read_contiguous(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_ACK_SEL_0), sel_masks_ack, msm_rpm_sel_mask_size); if (ctx_mask_ack & msm_rpm_get_ctx_mask(MSM_RPM_CTX_NOTIFICATION)) { struct msm_rpm_notification *n; int i; list_for_each_entry(n, &msm_rpm_notifications, list) for (i = 0; i < msm_rpm_sel_mask_size; i++) if (sel_masks_ack[i] & n->sel_masks[i]) { up(&n->sem); break; } msm_rpm_write_contiguous_zeros(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_ACK_SEL_0), msm_rpm_sel_mask_size); msm_rpm_write(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_ACK_CTX_0), 0); /* Ensure the write is complete before return */ mb(); return 1; } if (msm_rpm_request) { int i; *(msm_rpm_request->ctx_mask_ack) = ctx_mask_ack; memcpy(msm_rpm_request->sel_masks_ack, sel_masks_ack, sizeof(sel_masks_ack)); for (i = 0; i < msm_rpm_request->count; i++) msm_rpm_request->req[i].value = msm_rpm_read(MSM_RPM_PAGE_ACK, target_enum(msm_rpm_request->req[i].id)); msm_rpm_write_contiguous_zeros(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_ACK_SEL_0), msm_rpm_sel_mask_size); msm_rpm_write(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_ACK_CTX_0), 0); /* Ensure the write is complete before return */ mb(); if (msm_rpm_request->done) complete_all(msm_rpm_request->done); msm_rpm_request = NULL; return 0; } return 2; } /* * msm_rpm_sleep() * Sends idle/sleep interrupt to ARM, and then waits for wake up interrupt */ int msm_rpm_send_idle_command(void) { DECLARE_COMPLETION_ONSTACK(ack); unsigned long flags; wake_done = &ack; /* * Acquire RPM command mutex * This ensures that RPM resource requests, idle-set requests are all serialized */ mutex_lock(&msm_rpm_mutex); /* Protect "msm_rpm_request" access while the request is being sent * * Note: Currently the idle-set request is being sent as a simple interrupt * and not as resource request, which means msm_rpm_request is not used. * We are retaining the spinlock to keep the code standard and to allow * for future modifications (eg. if a parameter has to be passed to RPM with the idle-set * request * */ spin_lock_irqsave(&msm_rpm_lock, flags); /* * Get exclusive access to Krait-RPM IRQ registers */ spin_lock(&msm_rpm_irq_lock); msm_rpm_send_idle_interrupt(); spin_unlock(&msm_rpm_irq_lock); spin_unlock_irqrestore(&msm_rpm_lock, flags); pr_debug("Waiting for Wake from RPM \n"); wait_for_completion(&ack); /* * Release RPM command mutex */ mutex_unlock(&msm_rpm_mutex); pr_debug("Received Wake from RPM \n"); return 0; } EXPORT_SYMBOL(msm_rpm_send_idle_command); static void msm_rpm_err_fatal(void) { /* Tell RPM that we're handling the interrupt */ __raw_writel(0x1, msm_rpm_data.ipc_rpm_reg); panic("RPM error fataled"); } static irqreturn_t msm_rpm_err_interrupt(int irq, void *dev_id) { msm_rpm_err_fatal(); return IRQ_HANDLED; } static irqreturn_t msm_rpm_ack_interrupt(int irq, void *dev_id) { unsigned long flags; int rc; if (dev_id != &msm_rpm_ack_interrupt) return IRQ_NONE; spin_lock_irqsave(&msm_rpm_irq_lock, flags); rc = msm_rpm_process_ack_interrupt(); spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); return IRQ_HANDLED; } /* * Note: assumes caller has acquired . */ #ifdef NOIRQ_SUPPORT static void msm_rpm_busy_wait_for_request_completion( bool allow_async_completion) { int rc; do { while (!gic_is_irq_pending(msm_rpm_data.irq_ack) && msm_rpm_request) { if (allow_async_completion) spin_unlock(&msm_rpm_irq_lock); if (gic_is_irq_pending(msm_rpm_data.irq_err)) msm_rpm_err_fatal(); gic_clear_irq_pending(msm_rpm_data.irq_err); udelay(1); if (allow_async_completion) spin_lock(&msm_rpm_irq_lock); } if (!msm_rpm_request) break; rc = msm_rpm_process_ack_interrupt(); gic_clear_irq_pending(msm_rpm_data.irq_ack); } while (rc); } #endif /* Upon return, the array will contain values from the ack page. * * Note: assumes caller has acquired . * * Return value: * 0: success * -ENOSPC: request rejected */ static int msm_rpm_set_exclusive(int ctx, uint32_t *sel_masks, struct msm_rpm_iv_pair *req, int count) { DECLARE_COMPLETION_ONSTACK(ack); unsigned long flags; uint32_t ctx_mask = msm_rpm_get_ctx_mask(ctx); uint32_t ctx_mask_ack = 0; uint32_t sel_masks_ack[SEL_MASK_SIZE]; int i; msm_rpm_request_irq_mode.req = req; msm_rpm_request_irq_mode.count = count; msm_rpm_request_irq_mode.ctx_mask_ack = &ctx_mask_ack; msm_rpm_request_irq_mode.sel_masks_ack = sel_masks_ack; msm_rpm_request_irq_mode.done = &ack; spin_lock_irqsave(&msm_rpm_lock, flags); spin_lock(&msm_rpm_irq_lock); BUG_ON(msm_rpm_request); msm_rpm_request = &msm_rpm_request_irq_mode; for (i = 0; i < count; i++) { BUG_ON(target_enum(req[i].id) >= MSM_RPM_ID_LAST); msm_rpm_write(MSM_RPM_PAGE_REQ, target_enum(req[i].id), req[i].value); } msm_rpm_write_contiguous(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_REQ_SEL_0), sel_masks, msm_rpm_sel_mask_size); msm_rpm_write(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_REQ_CTX_0), ctx_mask); /* Ensure RPM data is written before sending the interrupt */ mb(); msm_rpm_send_req_interrupt(); spin_unlock(&msm_rpm_irq_lock); spin_unlock_irqrestore(&msm_rpm_lock, flags); if (!wait_for_completion_timeout(&ack, 3 * HZ)) panic("%s: Timeout while awaiting RPM response\n", __func__); BUG_ON((ctx_mask_ack & ~(msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED))) != ctx_mask); BUG_ON(memcmp(sel_masks, sel_masks_ack, sizeof(sel_masks_ack))); return (ctx_mask_ack & msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED)) ? -ENOSPC : 0; } /* Upon return, the array will contain values from the ack page. * * Note: assumes caller has acquired . * * Return value: * 0: success * -ENOSPC: request rejected */ #ifdef NOIRQ_SUPPORT static int msm_rpm_set_exclusive_noirq(int ctx, uint32_t *sel_masks, struct msm_rpm_iv_pair *req, int count) { unsigned int irq = msm_rpm_data.irq_ack; unsigned long flags; uint32_t ctx_mask = msm_rpm_get_ctx_mask(ctx); uint32_t ctx_mask_ack = 0; uint32_t sel_masks_ack[SEL_MASK_SIZE]; struct irq_chip *irq_chip, *err_chip; int i; msm_rpm_request_poll_mode.req = req; msm_rpm_request_poll_mode.count = count; msm_rpm_request_poll_mode.ctx_mask_ack = &ctx_mask_ack; msm_rpm_request_poll_mode.sel_masks_ack = sel_masks_ack; msm_rpm_request_poll_mode.done = NULL; spin_lock_irqsave(&msm_rpm_irq_lock, flags); irq_chip = irq_get_chip(irq); if (!irq_chip) { spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); return -ENOSPC; } irq_chip->irq_mask(irq_get_irq_data(irq)); err_chip = irq_get_chip(msm_rpm_data.irq_err); if (!err_chip) { irq_chip->irq_unmask(irq_get_irq_data(irq)); spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); return -ENOSPC; } err_chip->irq_mask(irq_get_irq_data(msm_rpm_data.irq_err)); if (msm_rpm_request) { msm_rpm_busy_wait_for_request_completion(true); BUG_ON(msm_rpm_request); } msm_rpm_request = &msm_rpm_request_poll_mode; for (i = 0; i < count; i++) { BUG_ON(target_enum(req[i].id) >= MSM_RPM_ID_LAST); msm_rpm_write(MSM_RPM_PAGE_REQ, target_enum(req[i].id), req[i].value); } msm_rpm_write_contiguous(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_REQ_SEL_0), sel_masks, msm_rpm_sel_mask_size); msm_rpm_write(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_REQ_CTX_0), ctx_mask); /* Ensure RPM data is written before sending the interrupt */ mb(); msm_rpm_send_req_interrupt(); msm_rpm_busy_wait_for_request_completion(false); BUG_ON(msm_rpm_request); err_chip->irq_unmask(irq_get_irq_data(msm_rpm_data.irq_err)); irq_chip->irq_unmask(irq_get_irq_data(irq)); spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); BUG_ON((ctx_mask_ack & ~(msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED))) != ctx_mask); BUG_ON(memcmp(sel_masks, sel_masks_ack, sizeof(sel_masks_ack))); return (ctx_mask_ack & msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED)) ? -ENOSPC : 0; } #else static int msm_rpm_set_exclusive_noirq(int ctx, uint32_t *sel_masks, struct msm_rpm_iv_pair *req, int count){ pr_err("no support for noirq_mode"); BUG(); return 0; } #endif /* Upon return, the array will contain values from the ack page. * * Return value: * 0: success * -EINVAL: invalid or invalid id in array * -ENOSPC: request rejected * -ENODEV: RPM driver not initialized */ static int msm_rpm_set_common( int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) { uint32_t sel_masks[SEL_MASK_SIZE] = {}; int rc; pr_debug("%s: ctx=%i (0x%x), count=%d, noirq=%d\n", __func__, ctx, ctx, count, noirq); for (rc = 0; rc < count; rc++) pr_debug("%s: req[%d] = { .id = %d (0x%x), .value = %d (0x%x), }\n", __func__, rc, req[rc].id, req[rc].id, req[rc].value, req[rc].value); if (ctx >= MSM_RPM_CTX_SET_COUNT) { rc = -EINVAL; goto set_common_exit; } rc = msm_rpm_fill_sel_masks(sel_masks, req, count); if (rc) goto set_common_exit; if (noirq) { unsigned long flags; spin_lock_irqsave(&msm_rpm_lock, flags); rc = msm_rpm_set_exclusive_noirq(ctx, sel_masks, req, count); spin_unlock_irqrestore(&msm_rpm_lock, flags); } else { mutex_lock(&msm_rpm_mutex); rc = msm_rpm_set_exclusive(ctx, sel_masks, req, count); mutex_unlock(&msm_rpm_mutex); } set_common_exit: return rc; } /* * Return value: * 0: success * -EINVAL: invalid or invalid id in array * -ENODEV: RPM driver not initialized. */ static int msm_rpm_clear_common( int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) { uint32_t sel_masks[SEL_MASK_SIZE] = {}; struct msm_rpm_iv_pair r[SEL_MASK_SIZE]; int rc; int i; if (ctx >= MSM_RPM_CTX_SET_COUNT) { rc = -EINVAL; goto clear_common_exit; } rc = msm_rpm_fill_sel_masks(sel_masks, req, count); if (rc) goto clear_common_exit; for (i = 0; i < ARRAY_SIZE(r); i++) { r[i].id = MSM_RPM_ID_INVALIDATE_0 + i; r[i].value = sel_masks[i]; } memset(sel_masks, 0, sizeof(sel_masks)); sel_masks[msm_rpm_get_sel_mask_reg(msm_rpm_data.sel_invalidate)] |= msm_rpm_get_sel_mask(msm_rpm_data.sel_invalidate); if (noirq) { unsigned long flags; spin_lock_irqsave(&msm_rpm_lock, flags); rc = msm_rpm_set_exclusive_noirq(ctx, sel_masks, r, ARRAY_SIZE(r)); spin_unlock_irqrestore(&msm_rpm_lock, flags); BUG_ON(rc); } else { mutex_lock(&msm_rpm_mutex); rc = msm_rpm_set_exclusive(ctx, sel_masks, r, ARRAY_SIZE(r)); mutex_unlock(&msm_rpm_mutex); BUG_ON(rc); } clear_common_exit: return rc; } /* * Note: assumes caller has acquired . */ static void msm_rpm_update_notification(uint32_t ctx, struct msm_rpm_notif_config *curr_cfg, struct msm_rpm_notif_config *new_cfg) { unsigned int sel_notif = msm_rpm_data.sel_notification; if (memcmp(curr_cfg, new_cfg, sizeof(*new_cfg))) { uint32_t sel_masks[SEL_MASK_SIZE] = {}; int rc; sel_masks[msm_rpm_get_sel_mask_reg(sel_notif)] |= msm_rpm_get_sel_mask(sel_notif); rc = msm_rpm_set_exclusive(ctx, sel_masks, new_cfg->iv, ARRAY_SIZE(new_cfg->iv)); BUG_ON(rc); memcpy(curr_cfg, new_cfg, sizeof(*new_cfg)); } } /* * Note: assumes caller has acquired . */ static void msm_rpm_initialize_notification(void) { struct msm_rpm_notif_config cfg; unsigned int ctx; int i; for (ctx = MSM_RPM_CTX_SET_0; ctx <= MSM_RPM_CTX_SET_SLEEP; ctx++) { cfg = msm_rpm_notif_cfgs[ctx]; for (i = 0; i < msm_rpm_sel_mask_size; i++) { configured_iv(&cfg)[i].id = MSM_RPM_ID_NOTIFICATION_CONFIGURED_0 + i; configured_iv(&cfg)[i].value = ~0UL; registered_iv(&cfg)[i].id = MSM_RPM_ID_NOTIFICATION_REGISTERED_0 + i; registered_iv(&cfg)[i].value = 0; } msm_rpm_update_notification(ctx, &msm_rpm_notif_cfgs[ctx], &cfg); } } /****************************************************************************** * Public functions *****************************************************************************/ int msm_rpm_local_request_is_outstanding(void) { unsigned long flags; int outstanding = 0; if (!spin_trylock_irqsave(&msm_rpm_lock, flags)) goto local_request_is_outstanding_exit; if (!spin_trylock(&msm_rpm_irq_lock)) goto local_request_is_outstanding_unlock; outstanding = (msm_rpm_request != NULL); spin_unlock(&msm_rpm_irq_lock); local_request_is_outstanding_unlock: spin_unlock_irqrestore(&msm_rpm_lock, flags); local_request_is_outstanding_exit: return outstanding; } /* * Read the specified status registers and return their values. * * status: array of id-value pairs. Each specifies a status register, * i.e, one of MSM_RPM_STATUS_ID_xxxx. Upon return, each will * contain the value of the status register. * count: number of id-value pairs in the array * * Return value: * 0: success * -EBUSY: RPM is updating the status page; values across different registers * may not be consistent * -EINVAL: invalid id in array * -ENODEV: RPM driver not initialized */ int msm_rpm_get_status(struct msm_rpm_iv_pair *status, int count) { uint32_t seq_begin; uint32_t seq_end; int rc; int i; seq_begin = msm_rpm_read(MSM_RPM_PAGE_STATUS, target_status(MSM_RPM_STATUS_ID_SEQUENCE)); for (i = 0; i < count; i++) { int target_status_id; if (status[i].id >= MSM_RPM_STATUS_ID_LAST) { pr_err("%s(): Status ID beyond limits\n", __func__); rc = -EINVAL; goto get_status_exit; } target_status_id = target_status(status[i].id); if (target_status_id >= MSM_RPM_STATUS_ID_LAST) { pr_err("%s(): Status id %d not defined for target\n", __func__, target_status_id); rc = -EINVAL; goto get_status_exit; } status[i].value = msm_rpm_read(MSM_RPM_PAGE_STATUS, target_status_id); } seq_end = msm_rpm_read(MSM_RPM_PAGE_STATUS, target_status(MSM_RPM_STATUS_ID_SEQUENCE)); rc = (seq_begin != seq_end || (seq_begin & 0x01)) ? -EBUSY : 0; get_status_exit: return rc; } EXPORT_SYMBOL(msm_rpm_get_status); /* * Issue a resource request to RPM to set resource values. * * Note: the function may sleep and must be called in a task context. * * ctx: the request's context. * There two contexts that a RPM driver client can use: * MSM_RPM_CTX_SET_0 and MSM_RPM_CTX_SET_SLEEP. For resource values * that are intended to take effect when the CPU is active, * MSM_RPM_CTX_SET_0 should be used. For resource values that are * intended to take effect when the CPU is not active, * MSM_RPM_CTX_SET_SLEEP should be used. * req: array of id-value pairs. Each specifies a RPM resource, * i.e, one of MSM_RPM_ID_xxxx. Each specifies the requested * resource value. * count: number of id-value pairs in the array * * Return value: * 0: success * -EINVAL: invalid or invalid id in array * -ENOSPC: request rejected * -ENODEV: RPM driver not initialized */ int msm_rpm_set(int ctx, struct msm_rpm_iv_pair *req, int count) { return msm_rpm_set_common(ctx, req, count, false); } EXPORT_SYMBOL(msm_rpm_set); /* * Issue a resource request to RPM to set resource values. * * Note: the function is similar to msm_rpm_set() except that it must be * called with interrupts masked. If possible, use msm_rpm_set() * instead, to maximize CPU throughput. */ int msm_rpm_set_noirq(int ctx, struct msm_rpm_iv_pair *req, int count) { WARN(!irqs_disabled(), "msm_rpm_set_noirq can only be called " "safely when local irqs are disabled. Consider using " "msm_rpm_set or msm_rpm_set_nosleep instead."); return msm_rpm_set_common(ctx, req, count, true); } EXPORT_SYMBOL(msm_rpm_set_noirq); /* * Issue a resource request to RPM to clear resource values. Once the * values are cleared, the resources revert back to their default values * for this RPM master. * * Note: the function may sleep and must be called in a task context. * * ctx: the request's context. * req: array of id-value pairs. Each specifies a RPM resource, * i.e, one of MSM_RPM_ID_xxxx. 's are ignored. * count: number of id-value pairs in the array * * Return value: * 0: success * -EINVAL: invalid or invalid id in array */ int msm_rpm_clear(int ctx, struct msm_rpm_iv_pair *req, int count) { return msm_rpm_clear_common(ctx, req, count, false); } EXPORT_SYMBOL(msm_rpm_clear); /* * Issue a resource request to RPM to clear resource values. * * Note: the function is similar to msm_rpm_clear() except that it must be * called with interrupts masked. If possible, use msm_rpm_clear() * instead, to maximize CPU throughput. */ int msm_rpm_clear_noirq(int ctx, struct msm_rpm_iv_pair *req, int count) { WARN(!irqs_disabled(), "msm_rpm_clear_noirq can only be called " "safely when local irqs are disabled. Consider using " "msm_rpm_clear or msm_rpm_clear_nosleep instead."); return msm_rpm_clear_common(ctx, req, count, true); } EXPORT_SYMBOL(msm_rpm_clear_noirq); /* * Register for RPM notification. When the specified resources * change their status on RPM, RPM sends out notifications and the * driver will "up" the semaphore in struct msm_rpm_notification. * * Note: the function may sleep and must be called in a task context. * * Memory for must not be freed until the notification is * unregistered. Memory for can be freed after this * function returns. * * n: the notifcation object. Caller should initialize only the * semaphore field. When a notification arrives later, the * semaphore will be "up"ed. * req: array of id-value pairs. Each specifies a status register, * i.e, one of MSM_RPM_STATUS_ID_xxxx. 's are ignored. * count: number of id-value pairs in the array * * Return value: * 0: success * -EINVAL: invalid id in array * -ENODEV: RPM driver not initialized */ int msm_rpm_register_notification(struct msm_rpm_notification *n, struct msm_rpm_iv_pair *req, int count) { unsigned long flags; unsigned int ctx; struct msm_rpm_notif_config cfg; int rc; int i; INIT_LIST_HEAD(&n->list); rc = msm_rpm_fill_sel_masks(n->sel_masks, req, count); if (rc) goto register_notification_exit; mutex_lock(&msm_rpm_mutex); if (!msm_rpm_init_notif_done) { msm_rpm_initialize_notification(); msm_rpm_init_notif_done = true; } spin_lock_irqsave(&msm_rpm_irq_lock, flags); list_add(&n->list, &msm_rpm_notifications); spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); ctx = MSM_RPM_CTX_SET_0; cfg = msm_rpm_notif_cfgs[ctx]; for (i = 0; i < msm_rpm_sel_mask_size; i++) registered_iv(&cfg)[i].value |= n->sel_masks[i]; msm_rpm_update_notification(ctx, &msm_rpm_notif_cfgs[ctx], &cfg); mutex_unlock(&msm_rpm_mutex); register_notification_exit: return rc; } EXPORT_SYMBOL(msm_rpm_register_notification); /* * Unregister a notification. * * Note: the function may sleep and must be called in a task context. * * n: the notifcation object that was registered previously. * * Return value: * 0: success * -ENODEV: RPM driver not initialized */ int msm_rpm_unregister_notification(struct msm_rpm_notification *n) { unsigned long flags; unsigned int ctx; struct msm_rpm_notif_config cfg; int rc = 0; int i; mutex_lock(&msm_rpm_mutex); ctx = MSM_RPM_CTX_SET_0; cfg = msm_rpm_notif_cfgs[ctx]; for (i = 0; i < msm_rpm_sel_mask_size; i++) registered_iv(&cfg)[i].value = 0; spin_lock_irqsave(&msm_rpm_irq_lock, flags); list_del(&n->list); list_for_each_entry(n, &msm_rpm_notifications, list) for (i = 0; i < msm_rpm_sel_mask_size; i++) registered_iv(&cfg)[i].value |= n->sel_masks[i]; spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); msm_rpm_update_notification(ctx, &msm_rpm_notif_cfgs[ctx], &cfg); mutex_unlock(&msm_rpm_mutex); return rc; } EXPORT_SYMBOL(msm_rpm_unregister_notification); static uint32_t fw_major, fw_minor, fw_build; static ssize_t driver_version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%u.%u.%u\n", msm_rpm_data.ver[0], msm_rpm_data.ver[1], msm_rpm_data.ver[2]); } static ssize_t fw_version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%u.%u.%u\n", fw_major, fw_minor, fw_build); } static struct kobj_attribute driver_version_attr = __ATTR_RO(driver_version); static struct kobj_attribute fw_version_attr = __ATTR_RO(fw_version); static struct attribute *driver_attributes[] = { &driver_version_attr.attr, &fw_version_attr.attr, NULL }; static struct attribute_group driver_attr_group = { .attrs = driver_attributes, }; static const struct of_device_id msm_rpm_of_match[] = { { .compatible = "qcom,rpm-ipq8064", .data = &ipq806x_rpm_data, }, { } }; MODULE_DEVICE_TABLE(of, msm_rpm_of_match); static void msm_rpm_fix_base_addr(struct msm_rpm_platform_data *pdata, void *base_addr, void *ipc_base_addr) { int i; for (i = 0; i < MSM_RPM_PAGE_COUNT; i++) { pdata->reg_base_addrs[i] = base_addr + (unsigned int)pdata->reg_base_addrs[i]; } pdata->ipc_rpm_reg += (unsigned int)ipc_base_addr; } static int msm_rpm_probe(struct platform_device *pdev) { struct msm_rpm_platform_data *data; const struct of_device_id *match; struct resource res; void *base_addr; void *ipc_base_addr; int ret; int switch_count; unsigned long long start_switch_hperf; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) { dev_err(&pdev->dev, "Unable to get memory!\n"); return -ENOMEM; } match = of_match_device(msm_rpm_of_match, &pdev->dev); memcpy(data, match->data, sizeof(*data)); data->dev = &pdev->dev; if (pdev->dev.platform_data) dev_warn(&pdev->dev, "%s: overriding platform_data (%p) by %p\n", __func__, pdev->dev.platform_data, match->data); if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { dev_err(&pdev->dev, "Unable to obtain register base address " "from device-tree\n"); return -ENODEV; } base_addr = devm_ioremap_resource(&pdev->dev, &res); if (IS_ERR(base_addr)) { dev_err(&pdev->dev, "Unable to map %pR\n", &res); return PTR_ERR(base_addr); } dev_info(&pdev->dev, "Mapped %pR to [0x%p]\n", &res, base_addr); ipc_base_addr = ioremap(0x02011000, 0x1000); if (IS_ERR(ipc_base_addr)) { dev_err(&pdev->dev, "Unable to map ipc regs \n"); return PTR_ERR(ipc_base_addr); } dev_info(&pdev->dev, "Mapped ipc region to [0x%p]\n", ipc_base_addr); data->direct_clk_reg_map = ioremap(0x900000, 0x4000); if (IS_ERR(data->direct_clk_reg_map)) { dev_err(&pdev->dev, "Unable to map direct_clk_regs \n"); return PTR_ERR( data->direct_clk_reg_map ); } dev_info(&pdev->dev, "Mapped direct_clk_regs to [0x%p]\n", data->direct_clk_reg_map); data->irq_ack = platform_get_irq_byname(pdev, "ack"); if (data->irq_ack < 0) { dev_err(&pdev->dev, "required ack interrupt missing\n"); return data->irq_ack; } data->irq_err = platform_get_irq_byname(pdev, "err"); if (data->irq_err < 0) { dev_err(&pdev->dev, "required err interrupt missing\n"); return data->irq_err; } data->irq_wakeup = platform_get_irq_byname(pdev, "wakeup"); if (data->irq_wakeup < 0) { dev_err(&pdev->dev, "required wakeup interrupt missing\n"); return data->irq_wakeup; } msm_rpm_fix_base_addr(data, base_addr, ipc_base_addr); ret = msm_rpm_init(data); if (ret) return ret; dev_info(&pdev->dev, "Initialising regulators\n"); rpm_setup_regulators(); rpm_set_regulator_voltages_high(); start_switch_hperf = jiffies; dev_info(&pdev->dev, "Switching NSS fabrics to high-performance\n"); switch_count = 0; do { switch_count++; if (switch_count > 100) panic("%s: Unable to set NSS fabrics to " "high-performance!\n", __func__); if (switch_count) dev_err_ratelimited(&pdev->dev, "Still trying to " "speed-up NSS fabrics... (> %d " "attempts)\n", switch_count); rpm_setup_nss_fabric_hperf(); msleep(20); schedule(); } while (!( rpm_nss_fb0_is_hperf() && rpm_nss_fb1_is_hperf())); dev_info(&pdev->dev, "it took us %llu jiffies (%d attempts) " "to switch nss fabrics to highperf\n", (jiffies - start_switch_hperf ), switch_count); data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) { dev_err(&pdev->dev, "Unable to get memory!\n"); return -ENOMEM; } match = of_match_device(msm_rpm_of_match, &pdev->dev); memcpy(data, match->data, sizeof(*data)); data->dev = &pdev->dev; if (pdev->dev.platform_data) dev_warn(&pdev->dev, "%s: overriding platform_data (%p) by %p\n", __func__, pdev->dev.platform_data, match->data); if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { dev_err(&pdev->dev, "Unable to obtain register base address " "from device-tree\n"); return -ENODEV; } pdev->dev.platform_data = &msm_rpm_data; return sysfs_create_group(&pdev->dev.kobj, &driver_attr_group); } static int msm_rpm_remove(struct platform_device *pdev) { sysfs_remove_group(&pdev->dev.kobj, &driver_attr_group); return 0; } static struct platform_driver msm_rpm_platform_driver = { .probe = msm_rpm_probe, .remove = msm_rpm_remove, .driver = { .name = "msm_rpm", .of_match_table = msm_rpm_of_match, }, }; static void msm_rpm_populate_map(struct msm_rpm_platform_data *data) { int i, j; struct msm_rpm_map_data *src = NULL; struct msm_rpm_map_data *dst = NULL; for (i = 0; i < MSM_RPM_ID_LAST;) { src = &data->target_id[i]; dst = &msm_rpm_data.target_id[i]; dst->id = MSM_RPM_ID_LAST; dst->sel = msm_rpm_data.sel_last + 1; /* * copy the target specific id of the current and also of * all the #count id's that follow the current. * [MSM_RPM_ID_PM8921_S1_0] = { MSM_RPM_8960_ID_PM8921_S1_0, * MSM_RPM_8960_SEL_PM8921_S1, * 2}, * [MSM_RPM_ID_PM8921_S1_1] = { 0, 0, 0 }, * should translate to * [MSM_RPM_ID_PM8921_S1_0] = { MSM_RPM_8960_ID_PM8921_S1_0, * MSM_RPM_8960_SEL_PM8921, * 2 }, * [MSM_RPM_ID_PM8921_S1_1] = { MSM_RPM_8960_ID_PM8921_S1_0 + 1, * MSM_RPM_8960_SEL_PM8921, * 0 }, */ for (j = 0; j < src->count; j++) { dst = &msm_rpm_data.target_id[i + j]; dst->id = src->id + j; dst->sel = src->sel; } i += (src->count) ? src->count : 1; } for (i = 0; i < MSM_RPM_STATUS_ID_LAST; i++) { if (data->target_status[i] & MSM_RPM_STATUS_ID_VALID) msm_rpm_data.target_status[i] &= ~MSM_RPM_STATUS_ID_VALID; else msm_rpm_data.target_status[i] = MSM_RPM_STATUS_ID_LAST; } } static irqreturn_t msm_pm_rpm_wakeup_interrupt(int irq, void *dev_id) { if (dev_id != &msm_pm_rpm_wakeup_interrupt) return IRQ_NONE; complete_all(wake_done); return IRQ_HANDLED; } int msm_rpm_init(struct msm_rpm_platform_data *data) { int rc; memcpy(&msm_rpm_data, data, sizeof(struct msm_rpm_platform_data)); msm_rpm_sel_mask_size = msm_rpm_data.sel_last / 32 + 1; BUG_ON(SEL_MASK_SIZE < msm_rpm_sel_mask_size); fw_major = msm_rpm_read(MSM_RPM_PAGE_STATUS, target_status(MSM_RPM_STATUS_ID_VERSION_MAJOR)); fw_minor = msm_rpm_read(MSM_RPM_PAGE_STATUS, target_status(MSM_RPM_STATUS_ID_VERSION_MINOR)); fw_build = msm_rpm_read(MSM_RPM_PAGE_STATUS, target_status(MSM_RPM_STATUS_ID_VERSION_BUILD)); pr_info("%s: RPM firmware %u.%u.%u\n", __func__, fw_major, fw_minor, fw_build); if (fw_major != msm_rpm_data.ver[0]) { pr_err("%s: RPM version %u.%u.%u incompatible with " "this driver version %u.%u.%u\n", __func__, fw_major, fw_minor, fw_build, msm_rpm_data.ver[0], msm_rpm_data.ver[1], msm_rpm_data.ver[2]); return -EFAULT; } msm_rpm_write(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_VERSION_MAJOR), msm_rpm_data.ver[0]); msm_rpm_write(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_VERSION_MINOR), msm_rpm_data.ver[1]); msm_rpm_write(MSM_RPM_PAGE_CTRL, target_ctrl(MSM_RPM_CTRL_VERSION_BUILD), msm_rpm_data.ver[2]); rc = request_irq(data->irq_ack, msm_rpm_ack_interrupt, IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND, "rpm_drv", msm_rpm_ack_interrupt); if (rc) { pr_err("%s: failed to request irq %d: %d\n", __func__, data->irq_ack, rc); return rc; } rc = irq_set_irq_wake(data->irq_ack, 1); if (rc) { dev_err(data->dev, "failed to set wakeup irq %u (error: %d)\n", data->irq_ack, rc); dev_warn(data->dev, "RPM will not be able to wake-up Linux " "via IRQ %d.\n", data->irq_ack); } rc = request_irq(data->irq_err, msm_rpm_err_interrupt, IRQF_TRIGGER_RISING, "rpm_err", NULL); if (rc) { pr_err("%s: failed to request error interrupt: %d\n", __func__, rc); return rc; } rc = request_irq(data->irq_wakeup, msm_pm_rpm_wakeup_interrupt, IRQF_TRIGGER_RISING, "pm_drv", msm_pm_rpm_wakeup_interrupt); if (rc) { pr_err("%s: failed to request irq %u: %d\n", __func__, data->irq_wakeup, rc); return rc; } rc = irq_set_irq_wake(data->irq_wakeup, 1); if (rc) { dev_err(data->dev, "failed to set wakeup irq %u (error: %d)\n", data->irq_wakeup, rc); dev_warn(data->dev, "RPM will not be able to wake-up Linux " "via IRQ %d.\n", data->irq_wakeup); } msm_rpm_populate_map(data); #ifdef CONFIG_DEBUG_FS rpm_debug_init(data); #endif return 0; } int rpm_debug_write(void *data __maybe_unused, u64 val) { struct msm_rpm_iv_pair req; req = (struct msm_rpm_iv_pair) { .value = val & 0xffffffff, .id = val >> 32, }; return msm_rpm_set(0, &req, 1); } #define NSSFB_CLK_SRC_CTL_NBID_SEL (1 << 0) #define NSSFB_CLK_SRC_PLL11_DIV2 0xb #define NSSFB_CLK_SRC_PLL11_DIV4 0x1b unsigned int rpm_nss_fb0_is_hperf(void){ volatile unsigned int nssfb0_clk_src_ctl = readl(msm_rpm_data.direct_clk_reg_map + 0x3B80); volatile unsigned int nssfb0_clk_src0_ns = readl(msm_rpm_data.direct_clk_reg_map + 0x3B84); volatile unsigned int nssfb0_clk_src1_ns = readl(msm_rpm_data.direct_clk_reg_map + 0x3B88); unsigned int ret; if ( nssfb0_clk_src_ctl & NSSFB_CLK_SRC_CTL_NBID_SEL){ ret = (nssfb0_clk_src1_ns == NSSFB_CLK_SRC_PLL11_DIV2 ); } else { ret = (nssfb0_clk_src0_ns == NSSFB_CLK_SRC_PLL11_DIV2 ); } if ( !ret ){ printk_once("nssfb0_clk_src_ctl=%#x, nssfb0_clk_src0_ns=%#x, nssfb0_clk_src1_ns=%#x \n", nssfb0_clk_src_ctl,nssfb0_clk_src0_ns, nssfb0_clk_src1_ns); } return ret; } unsigned int rpm_nss_fb1_is_hperf(void){ volatile unsigned int nssfb1_clk_src_ctl = readl(msm_rpm_data.direct_clk_reg_map + 0x3BE0); volatile unsigned int nssfb1_clk_src0_ns = readl(msm_rpm_data.direct_clk_reg_map + 0x3BE4); volatile unsigned int nssfb1_clk_src1_ns = readl(msm_rpm_data.direct_clk_reg_map + 0x3BE8); unsigned int ret; if ( nssfb1_clk_src_ctl & NSSFB_CLK_SRC_CTL_NBID_SEL){ ret = (nssfb1_clk_src1_ns == NSSFB_CLK_SRC_PLL11_DIV4); } else { ret = (nssfb1_clk_src0_ns == NSSFB_CLK_SRC_PLL11_DIV4); } if ( !ret ){ pr_err("nssfb1_clk_src_ctl=%#x, nssfb1_clk_src0_ns=%#x, nssfb1_clk_src1_ns=%#x \n", nssfb1_clk_src_ctl,nssfb1_clk_src0_ns, nssfb1_clk_src1_ns); } return ret; } void rpm_setup_nss_fabric_hperf(void) { msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 483, .value = 400000, }, }, 1); /* NSS0 Fabric Clock: 400 MHz */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 513, .value = 200000, }, }, 1); /* NSS1 (Fabric) Clock: 200 MHz */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 463, .value = 1150000, }, /* VDD UBI Core1 ?: 1150000, enableUBI-Core(?) */ { .id = 464, .value = 0, }, }, 1); /* smb208_s1b: 1.15V */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 36, .value = 532000, }, }, 1); /* Apps Fabric Clock: 533 MHz */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 43, .value = 532000, }, }, 1); msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 483, .value = 533000, }, }, 1); /* NSS0 Fabric Clock: 533 MHz */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 513, .value = 266500, }, }, 1); /* NSS1 (Fabric) Clock: 266 MHz */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 461, .value = 1150000, }, /* VDD UBI Core0 ? */ { .id = 462, .value = 0, }, }, 1); /* smb208_s1a: 1.15V */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 461, .value = 1150000, }, /* VDD UBI Core0 ? */ { .id = 462, .value = 0, }, }, 1); /* smb208_s1a: 1.15V */ msm_rpm_set(0, (struct msm_rpm_iv_pair []) { { .id = 36, .value = 533000, }, /* Apps Fabric Clock: 533 MHz */ { .id = 43, .value = 533000, }, }, 1); pr_err("[%s]\n", __func__); } struct rpm_request_info { const char *name; int context; struct msm_rpm_iv_pair *request; int count; bool noirq; }; static struct rpm_request_info regulator_requests[] = { { .name = "CXO clock enable(?) (init-sequence-1)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x22, .value = 0x01 }, }, .count = 1, }, { .name = "PXO clock enable(?) (init-sequence-2)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x23, .value = 0x01 }, }, .count = 1, }, { .name = "CXO clock enable(?) (init-sequence-3)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x22, .value = 0x01 }, }, .count = 1, }, { .name = "PXO clock enable(?) (init-sequence-4)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x23, .value = 0x01 }, }, .count = 1, }, { .name = "APPS Fabric clock enable(?) (init-sequence-5)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x24, .value = 0x20c49c }, }, .count = 1, }, { .name = "EBI1 clock enable(?) (init-sequence-6)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x2b, .value = 0x20c49c }, }, .count = 1, }, { .name = "EBI1 clock enable(?) (init-sequence-7)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x2b, .value = 0x20c49c }, }, .count = 1, }, { .name = "System Fabric clock enable(?) (init-sequence-8)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x25, .value = 0x20788 }, }, .count = 1, }, { .name = "Dayton Fabric clock enable(?) (init-sequence-9)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x27, .value = 0xfa00 }, }, .count = 1, }, { .name = "SFPB clock enable(?) (init-sequence-10)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x28, .value = 0xfa00 }, }, .count = 1, }, { .name = "RPM CTL ?? (init-sequence-11)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1b, .value = 0x02 }, }, .count = 1, }, { .name = "Dayton Fabric clock enable(?) (init-sequence-12)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x27, .value = 0xfa00 }, }, .count = 1, }, { .name = "Dayton Fabric clock enable(?) (init-sequence-13)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x27, .value = 0xfa00 }, }, .count = 1, }, { .name = "smb208_s1a (UBI Cores' clocks enable?) (init-sequence-14)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x4090c8e0 }, { .id = 0x1ce, .value = 0x18003ff }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.05V?) (init-sequence-15)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x100590 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.10V?) (init-sequence-16)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x10c8e0 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.05V?) (init-sequence-17)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x100590 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.10V?) (init-sequence-18)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x10c8e0 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.05V?) (init-sequence-19)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x100590 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.10V?) (init-sequence-20)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x10c8e0 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.05V?) (init-sequence-21)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x100590 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.10V?) (init-sequence-22)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x10c8e0 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.05V?) (init-sequence-23)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x100590 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.10V?) (init-sequence-24)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x10c8e0 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s1a (UBI Cores' clocks 1.05V?) (init-sequence-25)", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x100590 }, { .id = 0x1ce, .value = 0x00 }, }, .count = 2, }, { .name = "smb208_s2a:enable", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1d1, .value = 0x408fa3e8, }, { .id = 0x1d2, .value = 0x18003ff, }, }, .count = 2, }, { .name = "smb208_s2b:enable", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1d3, .value = 0x408e1d48 }, { .id = 0x1d4, .value = 0x18003ff }, }, .count = 2, }, { .name = "smb208_s2a:1100mV", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1d1, .value = 1100000, }, { .id = 0x1d2, .value = 0xc8e0 }, }, .count = 2, }, { .name = "smb208_s2b:1100mV", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1d3, .value = 1100000, }, { .id = 0x1d4, .value = 0x18c30, }, }, .count = 2, }, { .name = "late-init-1", .request = (struct msm_rpm_iv_pair []) { { .id = 0x22, .value = 0x01, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-2", .request = (struct msm_rpm_iv_pair []) { { .id = 0x22, .value = 0x00, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-3", .request = (struct msm_rpm_iv_pair []) { { .id = 0x2b, .value = 0x20c49c, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-4", .request = (struct msm_rpm_iv_pair []) { { .id = 0x2b, .value = 0x61a80, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-5", .request = (struct msm_rpm_iv_pair []) { { .id = 0x24, .value = 0x61a80, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-6", .request = (struct msm_rpm_iv_pair []) { { .id = 0x29, .value = 0xfa00, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-7", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x118c30, }, { .id = 0x1ce, .value = 0x00, }, }, .count = 2, .noirq = 0, }, { .name = "late-init-8", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x118c30, }, { .id = 0x1ce, .value = 0x00, }, }, .count = 2, .noirq = 0, }, { .name = "late-init-9", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1d1, .value = 0x11edd8, }, { .id = 0x1d2, .value = 0xc8e0, }, }, .count = 2, .noirq = 0, }, { .name = "late-init-10", .request = (struct msm_rpm_iv_pair []) { { .id = 0x24, .value = 0x82208, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-11", .request = (struct msm_rpm_iv_pair []) { { .id = 0x2b, .value = 0x82208, }, }, .count = 1, .noirq = 1, }, { .name = "late-init-12", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x118c30, }, { .id = 0x1ce, .value = 0x00, }, }, .count = 2, .noirq = 0, }, { .name = "late-init-13", .request = (struct msm_rpm_iv_pair []) { { .id = 0x1cd, .value = 0x118c30, }, { .id = 0x1ce, .value = 0x00, }, }, .count = 2, .noirq = 0, }, { } }; static void rpm_setup_regulators(void) { struct rpm_request_info *request; request = regulator_requests; while (request->request) { int ret; if (request->noirq) { request->noirq = 0; pr_notice("%s: RPM request %s %#x/%#x%s: ignoring " "'noirq' setting!\n", __func__, request->name ? : "(unnamed)", request->request[0].id, request->request[0].value, request->count > 1 ? "..." : ""); } ret = msm_rpm_set_common(request->context, request->request, request->count, request->noirq); if (ret) pr_warning("%s: RPM request %s %#x/%#x%s%s failed " "with %d\n", __func__, request->name ? : "(unnamed)", request->request[0].id, request->request[0].value, request->count > 1 ? "..." : "", request->noirq ? " (noirq)" : "", ret); else pr_debug("%s: RPM request %s %#x/%#x%s%s succeeded\n", __func__, request->name ? : "(unnamed)", request->request[0].id, request->request[0].value, request->count > 1 ? "..." : "", request->noirq ? " (noirq)" : ""); request++; } } int rpm_set_regulator_voltage(const char *name, unsigned milli_volts) { struct _set { const char *name; struct msm_rpm_iv_pair req[2]; } _set[] = { { .name = "smb208_s1a", .req = { { .id = 0x1cd, }, { .id = 0x1ce, .value = 0, }, /* TODO: verify */ }, }, { .name = "smb208_s1b", .req = { { .id = 0x1cf, }, { .id = 0x1d0, .value = 0, }, /* TODO: verify */ }, }, { .name = "smb208_s2a", .req = { { .id = 0x1d1, }, { .id = 0x1d2, .value = 0xc8e0, }, }, }, { .name = "smb208_s2b", .req = { { .id = 0x1d3, }, { .id = 0x1d4, .value = 0x18c30, }, }, }, { } }; struct _set *set; struct _set req; if (!name) return -EINVAL; for (set = _set; set->name && strcmp(set->name, name); set++) ; if (!set->name) return -EINVAL; memcpy(&req, set, sizeof(req)); req.req[0].value = milli_volts * 1000; return msm_rpm_set(0, req.req, 2); } static void rpm_set_regulator_voltages_high(void) { unsigned milli_volts; const int milli_volts_start = 1100; const int milli_volts_end = 1500; const int stepping = 10; const char *_set[] = { "smb208_s2a", "smb208_s2b", NULL }; int i; for (i = 0; i < 2; i++) { int ret = 0; const char *set = _set[i]; for (milli_volts = milli_volts_start; !ret && (milli_volts <= milli_volts_end); milli_volts += stepping) { ret = rpm_set_regulator_voltage(set, milli_volts); if (ret) break; } if (milli_volts > milli_volts_start) { pr_info("%s: %s: RPM requests for up to %umV " "succeeded\n", __func__, set, milli_volts - stepping); pr_debug("%s: %s: RPM request for %umV failed with " "%d)\n", __func__, set, milli_volts, ret); } } } static int msm_rpm_legacy_init(void) { return platform_driver_register(&msm_rpm_platform_driver); } module_init(msm_rpm_legacy_init);