diff options
author | Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> | 2010-05-10 18:08:53 +0200 |
---|---|---|
committer | John Rigby <john.rigby@linaro.org> | 2010-09-02 22:44:36 -0600 |
commit | 3ea2f3a7cd8e2d470bdaba599efb57968d7114a6 (patch) | |
tree | c84adc3431887fb0b515ec5265e86905c3cef0f5 /arch/arm/mach-ux500/prcmu-fw.c | |
parent | 747e3fcf01bb45034fe21512fe244d175559b493 (diff) |
ux500: move mach-u8500 to mach-ux500
Diffstat (limited to 'arch/arm/mach-ux500/prcmu-fw.c')
-rwxr-xr-x | arch/arm/mach-ux500/prcmu-fw.c | 1992 |
1 files changed, 1992 insertions, 0 deletions
diff --git a/arch/arm/mach-ux500/prcmu-fw.c b/arch/arm/mach-ux500/prcmu-fw.c new file mode 100755 index 00000000000..44208066790 --- /dev/null +++ b/arch/arm/mach-ux500/prcmu-fw.c @@ -0,0 +1,1992 @@ +/* + * Copyright 2009 ST-Ericsson. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +/* Routines to talk to PRCMU firmware services */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/ktime.h> +#include <linux/spinlock.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/prcmu-regs.h> +#include "prcmu-fw_ed.h" +#include "prcmu-fw_v1.h" + +#define NAME "PRCMU" +#define PM_DEBUG 0 +#define dbg_printk(format, arg...) (PM_DEBUG & 1) ? \ + (printk(KERN_ALERT NAME ": " format , ## arg)) : \ + ({do {} while (0); }) + +void __iomem *rtc_register_base ; + +struct hw_usage{ + char b2r2:1; + char mcde:1; + char esram1:1; + char esram2:1; + char esram3:1; + char esram4:1; +}; +struct hw_usage hw_usg_state = {0}; + +/* TASKLET declarations */ +static void prcmu_ack_mb7_status_tasklet(unsigned long); +DECLARE_TASKLET(prcmu_ack_mb7_tasklet, prcmu_ack_mb7_status_tasklet, 0); + +static void prcmu_ack_mb0_wkuph_status_tasklet(unsigned long); +DECLARE_TASKLET(prcmu_ack_mb0_wkuph_tasklet, \ + prcmu_ack_mb0_wkuph_status_tasklet, 0); + + +/* WAITQUEUES */ +DECLARE_WAIT_QUEUE_HEAD(ack_mb0_queue); +DECLARE_WAIT_QUEUE_HEAD(ack_mb5_queue); +DECLARE_WAIT_QUEUE_HEAD(ack_mb7_queue); + +/* Global var to determine if ca_wake_req is pending or not */ +static int ca_wake_req_pending; + +/* function pointer for shrm callback sequence for modem waking up arm */ +static void (*prcmu_ca_wake_req_shrm_callback)(u8); + +/* function pointer for shrm callback sequence for modem requesting reset */ +static void (*prcmu_modem_reset_shrm)(void); + +static int prcmu_set_hwacc_st(enum hw_acc_t hw_acc, enum hw_accst_t hw_accst); + +/* Internal functions */ + +/** + * _wait_for_req_complete () - used for PWRSTTRH for AckMb0 and AckMb1 on V1 + * @num: Mailbox number to operate on + * + * Polling loop to ensure that PRCMU FW has completed the requested op + */ +int _wait_for_req_complete(enum mailbox_t num) +{ + int timeout = 1000; + + if (u8500_is_earlydrop()) { + + /* Clear any error/status */ + writel(0, PRCM_ACK_MB0_ED); + /* Set an interrupt to XP70 */ + writel(1 << num, PRCM_MBOX_CPU_SET); + /* As of now only polling method, need to check if interrupt + * possible ?? TODO */ + while ((readl(PRCM_MBOX_CPU_VAL) & (1 << num)) && timeout--) + cpu_relax(); + + if (!timeout) + return -EBUSY; + else + return readl(PRCM_ACK_MB0_ED); + + + } else { + + /* checking any already on-going transaction */ + while ((readl(PRCM_MBOX_CPU_VAL) & (1 << num)) && timeout--) + cpu_relax(); + + timeout = 1000; + + + /* Clear any error/status */ + if (num == REQ_MB0) + writeb(0, PRCM_ACK_MB0_AP_PWRST_STATUS); + else if (num == REQ_MB1) + writel(0, PRCM_ACK_MB1); + else if (num == REQ_MB2) + writel(0, PRCM_ACK_MB2); + + + + /* Set an interrupt to XP70 */ + writel(1 << num, PRCM_MBOX_CPU_SET); + /* As of now only polling method, need to check if interrupt + * possible ?? TODO */ + while ((readl(PRCM_MBOX_CPU_VAL) & (1 << num)) && timeout--) + cpu_relax(); + + + if (!timeout) + return -EBUSY; + + return 0; + + } + +} + +/** + * prcmu_request_mailbox0 - request op on mailbox0 + * @req: mailbox0 type param + * + * Fill up the mailbox 0 required fields and call polling loop + */ +int prcmu_request_mailbox0(union req_mb0_t *req) +{ + if (u8500_is_earlydrop()) { + writel(req->complete_field, PRCM_REQ_MB0_ED); + return _wait_for_req_complete(REQ_MB0_ED); + + } else { + writel(req->complete_field, PRCM_REQ_MB0); + return _wait_for_req_complete(REQ_MB0); + } +} + +/** + * prcmu_request_mailbox1 - request op on mailbox1 + * @req: mailbox1 type param + * + * Fill up the mailbox 1 required fields and call polling loop + */ +int prcmu_request_mailbox1(union req_mb1_t *req) +{ + if (u8500_is_earlydrop()) { + writew(req->complete_field, PRCM_REQ_MB1_ED); + return _wait_for_req_complete(REQ_MB1_ED); + } else { + writew(req->complete_field, PRCM_REQ_MB1); + return _wait_for_req_complete(REQ_MB1); + } +} + +/** + * prcmu_request_mailbox2 - request op on mailbox2 + * @req: mailbox2 type param + * + * Fill up the mailbox 2 required fields and call polling loop + */ +int prcmu_request_mailbox2(union req_mb2_t *req) +{ + if (u8500_is_earlydrop()) { + writel(req->complete_field[0], PRCM_REQ_MB2_ED); + writel(req->complete_field[1], PRCM_REQ_MB2_ED + 4); + writel(req->complete_field[2], PRCM_REQ_MB2_ED + 8); + return _wait_for_req_complete(REQ_MB2_ED); + + } else { + writel(req->complete_field[0], PRCM_REQ_MB2); + writel(req->complete_field[1], PRCM_REQ_MB2 + 4); + writel(req->complete_field[2], PRCM_REQ_MB2 + 8); + return _wait_for_req_complete(REQ_MB2); + } + +} + +/* Exported APIs */ + +/** + * prcmu_get_boot_status - PRCMU boot status checking + * Returns: the current PRCMU boot status + */ +int prcmu_get_boot_status(void) +{ + if (u8500_is_earlydrop()) + return readb(PRCM_BOOT_STATUS_ED); + else + return readb(PRCM_BOOT_STATUS); +} +EXPORT_SYMBOL(prcmu_get_boot_status); + +/** + * prcmu_set_rc_a2p - This function is used to run few power state sequences + * @val: Value to be set, i.e. transition requested + * Returns: 0 on success, -EINVAL on invalid argument + * + * This function is used to run the following power state sequences - + * any state to ApReset, ApDeepSleep to ApExecute, ApExecute to ApDeepSleep + */ +int prcmu_set_rc_a2p(enum romcode_write_t val) +{ + if (u8500_is_earlydrop()) { + if (val < RDY_2_DS_ED || val > RDY_2_XP70_RST_ED) + return -EINVAL; + writeb(val, PRCM_ROMCODE_A2P_ED); + return 0; + } + + + if (val < RDY_2_DS || val > RDY_2_XP70_RST) + return -EINVAL; + writeb(val, PRCM_ROMCODE_A2P); + return 0; +} +EXPORT_SYMBOL(prcmu_set_rc_a2p); + +/** + * prcmu_get_rc_p2a - This function is used to get power state sequences + * Returns: the power transition that has last happened + * + * This function can return the following transitions- + * any state to ApReset, ApDeepSleep to ApExecute, ApExecute to ApDeepSleep + */ +enum romcode_read_t prcmu_get_rc_p2a(void) +{ + if (u8500_is_earlydrop()) + return readb(PRCM_ROMCODE_P2A_ED); + + + return readb(PRCM_ROMCODE_P2A); +} +EXPORT_SYMBOL(prcmu_get_rc_p2a); + +/** + * prcmu_get_current_mode - Return the current XP70 power mode + * Returns: Returns the current AP(ARM) power mode: init, + * apBoot, apExecute, apDeepSleep, apSleep, apIdle, apReset + */ +enum ap_pwrst_t prcmu_get_xp70_current_state(void) +{ + if (u8500_is_earlydrop()) + return readb(PRCM_AP_PWR_STATE_ED); + + + return readb(PRCM_XP70_CUR_PWR_STATE); +} +EXPORT_SYMBOL(prcmu_get_xp70_current_state); + +/** + * prcmu_set_ap_mode - set the appropriate AP power mode + * @ap_pwrst_trans: Transition to be requested to move to new AP power mode + * Returns: 0 on success, non-zero on failure + * + * This function set the appropriate AP power mode. + * The caller can check the status following this call. + */ +int prcmu_set_ap_mode(enum ap_pwrst_trans_t ap_pwrst_trans) +{ + union req_mb0_t request = { {0} }; + + if (u8500_is_earlydrop()) { + if (ap_pwrst_trans + && (ap_pwrst_trans < APEXECUTE_TO_APSLEEP_ED + || ap_pwrst_trans > APEXECUTE_TO_APIDLE_ED)) + return -EINVAL; + request.req_field.ap_pwrst_trans = ap_pwrst_trans; + return prcmu_request_mailbox0(&request); + } + + if (ap_pwrst_trans + && (ap_pwrst_trans < APEXECUTE_TO_APSLEEP + || ap_pwrst_trans > APEXECUTE_TO_APIDLE)) + return -EINVAL; + request.req_field.ap_pwrst_trans = ap_pwrst_trans; + return prcmu_request_mailbox0(&request); +} +EXPORT_SYMBOL(prcmu_set_ap_mode); + +/** + * prcmu_set_fifo_4500wu - Configures 4500 fifo interrupt as wake-up events + * @fifo_4500wu: The 4500 fifo interrupt to be configured as wakeup or not + * Returns: 0 on success, non-zero on failure + * + * This function de/configures 4500 fifo interrupt as wake-up events + * The caller can check the status following this call. + */ +int prcmu_set_fifo_4500wu(enum intr_wakeup_t fifo_4500wu) +{ + union req_mb0_t request = { {0} }; + + if (u8500_is_earlydrop()) { + if (fifo_4500wu < INTR_NOT_AS_WAKEUP_ED \ + || fifo_4500wu > INTR_AS_WAKEUP_ED) + return -EINVAL; + request.req_field.fifo_4500wu = fifo_4500wu; + return prcmu_request_mailbox0(&request); + } + + if (fifo_4500wu < INTR_NOT_AS_WAKEUP || fifo_4500wu > INTR_AS_WAKEUP) + return -EINVAL; + request.req_field.fifo_4500wu = fifo_4500wu; + return prcmu_request_mailbox0(&request); +} +EXPORT_SYMBOL(prcmu_set_fifo_4500wu); + +/** + * prcmu_set_ddr_pwrst - set the appropriate DDR power mode + * @ddr_pwrst: Power mode of DDR to which DDR needs to be switched + * Returns: 0 on success, non-zero on failure + * + * This function is not supported on ED by the PRCMU firmware + */ +int prcmu_set_ddr_pwrst(enum ddr_pwrst_t ddr_pwrst) +{ + union req_mb0_t request = { {0} }; + + if (u8500_is_earlydrop()) { + if (ddr_pwrst < DDR_PWR_STATE_UNCHANGED_ED || \ + ddr_pwrst > TOBEDEFINED_ED) + return -EINVAL; + request.req_field.ddr_pwrst = ddr_pwrst; + return prcmu_request_mailbox0(&request); + } + + if (ddr_pwrst < DDR_PWR_STATE_UNCHANGED || \ + ddr_pwrst > DDR_PWR_STATE_OFFHIGHLAT) + return -EINVAL; + request.req_field.ddr_pwrst = ddr_pwrst; + return prcmu_request_mailbox0(&request); +} +EXPORT_SYMBOL(prcmu_set_ddr_pwrst); + +/** + * prcmu_set_arm_opp - set the appropriate ARM OPP + * @arm_opp: The new ARM operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function set the appropriate ARM operation mode + * The caller can check the status following this call. + * NOTE : THE PRCMU DOES NOT ISSUE AN IT TO ARM FOR A DVFS + * XISITION REQUEST. HENCE WE DO NOT NEED A MAILBOX + * TIMEOUT WAIT HERE FOR OPP DVFS + */ +int prcmu_set_arm_opp(enum arm_opp_t arm_opp) +{ + int timeout = 200; + enum arm_opp_t current_arm_opp; + + if (u8500_is_earlydrop()) { + if (arm_opp < ARM_NO_CHANGE_ED || arm_opp > ARM_EXTCLK_ED) + return -EINVAL; + /* check for any ongoing AP state transitions */ + while ((readl(PRCM_MBOX_CPU_VAL) & 1) && timeout--) + cpu_relax(); + if (!timeout) + return -EBUSY; + /* check for any ongoing ARM DVFS */ + timeout = 200; + while ((readb(PRCM_M2A_DVFS_STAT_ED) == 0xff) && timeout--) + cpu_relax(); + if (!timeout) + return -EBUSY; + + /* Clear any error/status */ + writel(0, PRCM_ACK_MB0_ED); + + writeb(arm_opp, PRCM_ARM_OPP_ED); + writeb(APE_NO_CHANGE, PRCM_APE_OPP_ED); + writel(2, PRCM_MBOX_CPU_SET); + + timeout = 1000; + while ((readl(PRCM_MBOX_CPU_VAL) & 2) && timeout--) + cpu_relax(); + + /* TODO: read mbox to ARM error here.. */ + return 0; + } + + + if (arm_opp < ARM_NO_CHANGE || arm_opp > ARM_EXTCLK) + return -EINVAL; + + /* check for any ongoing AP state transitions */ + while ((readl(PRCM_MBOX_CPU_VAL) & 1) && timeout--) + cpu_relax(); + if (!timeout) + return -EBUSY; + + /* check for any ongoing ARM DVFS */ + timeout = 200; + while ((readb(PRCM_ACK_MB1_CURR_DVFS_STATUS) == 0xff) && timeout--) + cpu_relax(); + if (!timeout) + return -EBUSY; + + /* clear the mailbox */ + writel(0, PRCM_ACK_MB1); + + /* write 0x0 into the header for ARM/APE operating point mgmt */ + writel(0x0, _PRCM_MBOX_HEADER); + + /* write ARMOPP request into the mailbox */ + writel(arm_opp, PRCM_REQ_MB1_ARMOPP); + writel(APE_NO_CHANGE, PRCM_REQ_MB1_APEOPP); + + _wait_for_req_complete(REQ_MB1); + + current_arm_opp = readb(PRCM_ACK_MB1_CURR_ARMOPP); + if (arm_opp != current_arm_opp) { + printk(KERN_WARNING + "u8500-prcm : requested ARM DVFS Not Complete\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(prcmu_set_arm_opp); + +/** + * prcmu_get_arm_opp - get the appropriate ARM OPP + * + * Returns: the current ARM OPP + */ +int prcmu_get_arm_opp(void) +{ + return readb(PRCM_ACK_MB1_CURR_ARMOPP); +} +EXPORT_SYMBOL(prcmu_get_arm_opp); + +/** + * prcmu_set_ape_opp - set the appropriate APE OPP + * @ape_opp: The new APE operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function set the appropriate APE operation mode + * The caller can check the status following this call. + */ +int prcmu_set_ape_opp(enum ape_opp_t ape_opp) +{ + int timeout = 200; + enum ape_opp_t current_ape_opp; + + if (ape_opp < APE_NO_CHANGE || ape_opp > APE_50_OPP) + return -EINVAL; + + /* check for any ongoing AP state transitions */ + while ((readl(PRCM_MBOX_CPU_VAL) & 1) && timeout--) + cpu_relax(); + if (!timeout) + return -EBUSY; + + /* check for any ongoing ARM DVFS */ + timeout = 200; + while ((readb(PRCM_ACK_MB1_CURR_DVFS_STATUS) == 0xff) && timeout--) + cpu_relax(); + if (!timeout) + return -EBUSY; + + /* clear the mailbox */ + writel(0, PRCM_ACK_MB1); + + /* write 0x0 into the header for ARM/APE operating point mgmt */ + writel(0x0, _PRCM_MBOX_HEADER); + + /* write ARMOPP request into the mailbox */ + writel(ARM_NO_CHANGE, PRCM_REQ_MB1_ARMOPP); + writel(ape_opp, PRCM_REQ_MB1_APEOPP); + + /* trigger the XP70 IT11 to the XP70 */ + writel(PRCM_XP70_TRIG_IT11, PRCM_MBOX_CPU_SET); + + timeout = 1000; + while ((readl(PRCM_MBOX_CPU_VAL) & PRCM_XP70_TRIG_IT11) + && timeout--) + cpu_relax(); + + current_ape_opp = readb(PRCM_ACK_MB1_CURR_APEOPP); + if (ape_opp != current_ape_opp) { + printk(KERN_WARNING + "u8500-prcm : requested APE DVFS Not Complete\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(prcmu_set_ape_opp); + +/** + * prcmu_get_arm_opp - get the appropriate ARM OPP + * + * Returns: the current ARM OPP + */ +int prcmu_get_ape_opp(void) +{ + return readb(PRCM_ACK_MB1_CURR_APEOPP); +} +EXPORT_SYMBOL(prcmu_get_ape_opp); + + +int prcmu_set_hwacc(enum hw_acc_dev hw_acc, enum hw_accst_t hw_accst) +{ + enum hw_acc_t hw_acc_device; + + /* check on which device is being used */ + switch (hw_acc) { + case HW_ACC_SVAMMDSP: + hw_acc_device = SVAMMDSP; + break; + case HW_ACC_SVAPIPE: + hw_acc_device = SVAPIPE; + break; + case HW_ACC_SIAMMDSP: + hw_acc_device = SIAMMDSP; + break; + case HW_ACC_SIAPIPE: + hw_acc_device = SIAPIPE; + break; + case HW_ACC_SGA: + hw_acc_device = SGA; + break; + case HW_ACC_B2R2: + if (hw_accst == HW_ON) + hw_usg_state.b2r2 = 1; + else if (hw_accst == HW_OFF) + hw_usg_state.b2r2 = 0; + + if (hw_usg_state.mcde == 1) + return -EINVAL; + hw_acc_device = B2R2MCDE; + break; + case HW_ACC_MCDE: + if (hw_accst == HW_ON) + hw_usg_state.mcde = 1; + else if (hw_accst == HW_OFF) + hw_usg_state.mcde = 0; + + if (hw_usg_state.b2r2 == 1) + return -EINVAL; + hw_acc_device = B2R2MCDE; + break; + case HW_ACC_ESRAM1: + if (hw_accst == HW_ON) + hw_usg_state.esram1 = 1; + else if (hw_accst == HW_OFF) + hw_usg_state.esram1 = 0; + + if (hw_usg_state.esram2 == 1) + return -EINVAL; + hw_acc_device = ESRAM1; + break; + case HW_ACC_ESRAM2: + if (hw_accst == HW_ON) + hw_usg_state.esram2 = 1; + else if (hw_accst == HW_OFF) + hw_usg_state.esram2 = 0; + + if (hw_usg_state.esram1 == 1) + return -EINVAL; + hw_acc_device = ESRAM2; + break; + case HW_ACC_ESRAM3: + if (hw_accst == HW_ON) + hw_usg_state.esram3 = 1; + else if (hw_accst == HW_OFF) + hw_usg_state.esram3 = 0; + + if (hw_usg_state.esram4 == 1) + return -EINVAL; + hw_acc_device = ESRAM3; + break; + case HW_ACC_ESRAM4: + if (hw_accst == HW_ON) + hw_usg_state.esram4 = 1; + else if (hw_accst == HW_OFF) + hw_usg_state.esram4 = 0; + + if (hw_usg_state.esram3 == 1) + return -EINVAL; + hw_acc_device = ESRAM4; + break; + default: + return -EINVAL; + }; + return prcmu_set_hwacc_st(hw_acc_device, hw_accst); +} +EXPORT_SYMBOL(prcmu_set_hwacc); + + +/** + * prcmu_set_ape_opp - set the appropriate h/w accelerator to power mode + * @hw_acc: the hardware accelerator being considered + * @hw_accst: The new h/w accelerator state(on/off/retention) + * Returns: 0 on success, non-zero on failure + * + * This function set the appropriate hardware accelerator to the requested + * power mode. + * The caller can check the status following this call. + */ +static int prcmu_set_hwacc_st(enum hw_acc_t hw_acc, enum hw_accst_t hw_accst) +{ + union req_mb2_t request; + int err = 0; + if (u8500_is_earlydrop()) { + if (hw_acc < SVAMMDSP_ED || hw_acc > ESRAM4_ED || + hw_accst < HW_NO_CHANGE_ED || hw_accst \ + > HW_ON_ED) + return -EINVAL; + memset(&request, 0x0, sizeof(request)); + request.hw_accst_list[hw_acc].hw_accst = hw_accst; + return prcmu_request_mailbox2(&request); + } + + if (hw_acc < SVAMMDSP || hw_acc > ESRAM4 || + hw_accst < HW_NO_CHANGE || hw_accst > HW_ON) + return -EINVAL; + + /* write the header into mailbox 2 */ + writeb(DPS_H, PRCM_MBOX_HEADER_REQ_MB2); + /* fill out the request */ + writel(0x0, PRCM_REQ_MB2_DPS_SVAMMDSP); + writel(0x0, PRCM_REQ_MB2_DPS_SGA); + writeb(hw_accst, PRCM_REQ_MB2 + hw_acc); + /* request for completion */ + err = _wait_for_req_complete(REQ_MB2); + printk("\n readb(PRCM_ACK_MB2) = %x\n", readb(PRCM_ACK_MB2));\ + if (readb(PRCM_ACK_MB2) == HWACC_PWRST_OK) + err = 0; + else + err = -EINVAL; + return err; +} + +/** + * prcmu_get_m2a_status - Get the status or transition of the last request + * Returns: The status from MBOX to ARM during the last request. + * See the list of status/transitions for details. + */ +enum mbox_2_arm_stat_ed_t prcmu_get_m2a_status(void) +{ + return readb(PRCM_M2A_STATUS_ED); +} +EXPORT_SYMBOL(prcmu_get_m2a_status); + +/** + * prcmu_get_m2a_error - Get the error that occured in last request + * Returns: The error from MBOX to ARM during the last request. + * See the list of errors for details. + */ +enum mbox_to_arm_err_ed_t prcmu_get_m2a_error(void) +{ + return readb(PRCM_M2A_ERROR_ED); +} +EXPORT_SYMBOL(prcmu_get_m2a_error); + +/** + * prcmu_get_m2a_dvfs_status - Get the state of DVFS transition + * Returns: Either that state transition on DVFS is on going + * or completed, 0 if not used. + */ +enum dvfs_stat_t prcmu_get_m2a_dvfs_status(void) +{ + if (u8500_is_earlydrop()) + return readb(PRCM_M2A_DVFS_STAT_ED); + + + return 0; +} +EXPORT_SYMBOL(prcmu_get_m2a_dvfs_status); + +/** + * prcmu_get_m2a_hwacc_status - Get the state of HW Accelerator + * Returns: Either that state transition on hardware accelerator + * is on going or completed, 0 if not used. + */ +enum mbox_2_arm_hwacc_pwr_stat_t prcmu_get_m2a_hwacc_status(void) +{ + if (u8500_is_earlydrop()) + return readb(PRCM_M2A_HWACC_STAT_ED); + + + return 0; +} +EXPORT_SYMBOL(prcmu_get_m2a_hwacc_status); + +/** + * prcmu_set_irq_wakeup - set the ARM IRQ wake-up events + * @irq: ARM IRQ that needs to be set as wake up source + * Returns: 0 on success, -EINVAL on invalid argument + */ +int prcmu_set_irq_wakeup(uint32_t irq) +{ + unsigned long mask_reg; + + + if (irq < 32) { + mask_reg = PRCM_ARMITMSK31TO0; + } else if (irq < 64) { + mask_reg = PRCM_ARMITMSK63TO32; + irq -= 32; + } else if (irq < 96) { + mask_reg = PRCM_ARMITMSK95TO64; + irq -= 64; + } else if (irq < 128) { + mask_reg = PRCM_ARMITMSK127TO96; + irq -= 96; + } else + return -EINVAL; + + writel(1 << irq | readl((volatile unsigned long *)(mask_reg)), + (volatile unsigned long *)(mask_reg)); + return 0; +} +EXPORT_SYMBOL(prcmu_set_irq_wakeup); + +/* FIXME : Make these generic enough to pass backup + * addresses for re-using the same functions + * for sleep/deep sleep modes + */ +/* Context Backups for ApSleep */ +unsigned int PRCC_backup[15]; + +#define _PRCC_CLK_RST1_BASE IO_ADDRESS(U8500_PER1_BASE + 0xF000) +#define _PRCC_CLK_RST2_BASE IO_ADDRESS(U8500_PER2_BASE + 0xF000) +#define _PRCC_CLK_RST3_BASE IO_ADDRESS(U8500_PER3_BASE + 0xF000) +#define _PRCC_CLK_RST5_BASE IO_ADDRESS(U8500_PER5_BASE + 0x1F000) +#define _PRCC_CLK_RST6_BASE IO_ADDRESS(U8500_PER6_BASE + 0xF000) +#define _PRCC_CLK_RST7_BASE IO_ADDRESS(U8500_PER7_BASE_ED + 0xF000) + +void pm_save_config_PRCC(void) +{ + uint8_t i = 0; + + PRCC_backup[i++] = readl(_PRCC_CLK_RST1_BASE + 0x10); + PRCC_backup[i++] = readl(_PRCC_CLK_RST1_BASE + 0x14); + PRCC_backup[i++] = readl(_PRCC_CLK_RST2_BASE + 0x10); + PRCC_backup[i++] = readl(_PRCC_CLK_RST2_BASE + 0x14); + PRCC_backup[i++] = readl(_PRCC_CLK_RST3_BASE + 0x10); + PRCC_backup[i++] = readl(_PRCC_CLK_RST3_BASE + 0x14); + PRCC_backup[i++] = readl(_PRCC_CLK_RST5_BASE + 0x10); + PRCC_backup[i++] = readl(_PRCC_CLK_RST5_BASE + 0x14); + PRCC_backup[i++] = readl(_PRCC_CLK_RST6_BASE + 0x10); + PRCC_backup[i++] = readl(_PRCC_CLK_RST6_BASE + 0x14); + PRCC_backup[i++] = readl(_PRCC_CLK_RST7_BASE + 0x10); + PRCC_backup[i++] = readl(_PRCC_CLK_RST7_BASE + 0x14); +} + +void pm_restore_config_PRCC(void) +{ + uint8_t i = 0; + + writel(PRCC_backup[i++], _PRCC_CLK_RST1_BASE + 0x0); + writel(PRCC_backup[i++], _PRCC_CLK_RST1_BASE + 0x8); + writel(PRCC_backup[i++], _PRCC_CLK_RST2_BASE + 0x0); + writel(PRCC_backup[i++], _PRCC_CLK_RST2_BASE + 0x8); + writel(PRCC_backup[i++], _PRCC_CLK_RST3_BASE + 0x0); + writel(PRCC_backup[i++], _PRCC_CLK_RST3_BASE + 0x8); + writel(PRCC_backup[i++], _PRCC_CLK_RST5_BASE + 0x0); + writel(PRCC_backup[i++], _PRCC_CLK_RST5_BASE + 0x8); + writel(PRCC_backup[i++], _PRCC_CLK_RST6_BASE + 0x0); + writel(PRCC_backup[i++], _PRCC_CLK_RST6_BASE + 0x8); + writel(PRCC_backup[i++], _PRCC_CLK_RST7_BASE + 0x0); + writel(PRCC_backup[i++], _PRCC_CLK_RST7_BASE + 0x8); +} + +void __iomem *uart_backup_base, *uart_register_base; + +/* TODO : return success value to let decide to proceed + * for low power request or not + */ +void pm_save_config_UART(void) +{ + uart_register_base = ioremap(U8500_UART2_BASE, SZ_4K); + if (!uart_register_base) { + printk(KERN_WARNING + "u8500-prcm : cannot allocate uart register base\n"); + } + + uart_backup_base = kzalloc(SZ_4K, GFP_KERNEL); + if (!uart_backup_base) { + printk(KERN_WARNING + "u8500-prcm : cannot allocate uart register backup base\n"); + } + + /* Copy UART config */ + memcpy(uart_backup_base, uart_register_base, SZ_4K); +} + +void __iomem *ab8500_backup_base, *ab8500_register_base; + +void pm_restore_config_UART(void) +{ + memcpy(uart_register_base, uart_backup_base, SZ_4K); +} + +/* Deep Sleep context save */ + +/* public context backup */ +/* TODO : remove hard coded backup RAM addresses */ +void a9ss_save_public_config(void) +{ + int val; + + /* this happens for CPU0 only as of now */ + /* CP15 SCTLR */ + __asm__ __volatile__( + "mrc p15, 0, %0, c1, c1, 2" + : "=r" (val) + : + : "cc"); + writel(val, IO_ADDRESS(0x80151F80)); + + /* CP15 TTBR0 */ + __asm__ __volatile__( + "mrc p15, 0, %0, c2, c0, 0" + : "=r" (val) + : + : "cc"); + writel(val, IO_ADDRESS(0x80151F84)); + + /* CP15 TTBR1 */ + __asm__ __volatile__( + "mrc p15, 0, %0, c2, c0, 1" + : "=r" (val) + : + : "cc"); + writel(val, IO_ADDRESS(0x80151F88)); + + /* CP15 TTBCR */ + __asm__ __volatile__( + "mrc p15, 0, %0, c2, c0, 2" + : "=r" (val) + : + : "cc"); + writel(val, IO_ADDRESS(0x80151F8C)); + + /* CP15 DACR */ + __asm__ __volatile__( + "mrc p15, 0, %0, c3, c0, 0" + : "=r" (val) + : + : "cc"); + writel(val, IO_ADDRESS(0x80151F90)); + + /* CPSR */ + __asm__ __volatile__( + "mrs %0, cpsr" + : "=r" (val) + : + : "cc"); + writel(val, IO_ADDRESS(0x80151F98)); + + return; +} + +void a9ss_restore_public_config(void) +{ + int val; + + /* CPSR */ + val = readl(IO_ADDRESS(0x80151F98)); + __asm__ __volatile__( + "msr cpsr, %0" + : + : "r" (val)); + + /* CP15 DACR */ + val = readl(IO_ADDRESS(0x80151F90)); + __asm__ __volatile__( + "mcr p15, 0, %0, c3, c0, 0" + : + : "r" (val)); + + /* CP15 TTBCR */ + val = readl(IO_ADDRESS(0x80151F8C)); + __asm__ __volatile__( + "mcr p15, 0, %0, c2, c0, 2" + : + : "r" (val)); + + /* CP15 TTBR1 */ + val = readl(IO_ADDRESS(0x80151F88)); + __asm__ __volatile__( + "mcr p15, 0, %0, c2, c0, 1" + : + : "r" (val)); + + /* CP15 TTBR0 */ + val = readl(IO_ADDRESS(0x80151F84)); + __asm__ __volatile__( + "mcr p15, 0, %0, c2, c0, 0" + : + : "r" (val)); + + /* CP15 SCTLR */ + val = readl(IO_ADDRESS(0x80151F80)); + __asm__ __volatile__( + "mrc p15, 0, %0, c1, c1, 2" + : + : "r" (val)); + + return; +} + +/* ARM SCU backup */ +#include <mach/scu.h> +unsigned int scu_backup_config[5]; + +void a9ss_save_scu_config(void) +{ + scu_backup_config[0] = __raw_readl(IO_ADDRESS(U8500_SCU_BASE) + + SCU_CTRL); + scu_backup_config[1] = __raw_readl(IO_ADDRESS(U8500_SCU_BASE) + + SCU_CONFIG); + scu_backup_config[2] = __raw_readl(IO_ADDRESS(U8500_SCU_BASE) + + SCU_CPU_STATUS); + scu_backup_config[3] = __raw_readl(IO_ADDRESS(U8500_SCU_BASE) + + SCU_INVALIDATE); + + return; +} + +void a9ss_restore_scu_config(void) +{ + __raw_writel(scu_backup_config[0], IO_ADDRESS(U8500_SCU_BASE) + + SCU_CTRL); + __raw_writel(scu_backup_config[1], IO_ADDRESS(U8500_SCU_BASE) + + SCU_CONFIG); + __raw_writel(scu_backup_config[2], IO_ADDRESS(U8500_SCU_BASE) + + SCU_CPU_STATUS); + __raw_writel(scu_backup_config[3], IO_ADDRESS(U8500_SCU_BASE) + + SCU_INVALIDATE); + + return; +} + +/** + * prcmu_apply_ap_state_transition - PRCMU State Transition function + * @transition: Transition to be requested to move to new AP power mode + * @ddr_state_req: Power mode of DDR to which DDR needs to be switched + * @_4500_fifo_wakeup: 4500 fifo interrupt to be configured as wakeup or not + * Returns: 0 on success, -EINVAL on failure + * + * NOTES: This routine assumes that ARM side housekeeping that + * needs to be done before entering sleep/deep-sleep, eg. the routines + * begin(), prepare() in platform_suspend_ops have done their job + * this routine only makes the required state transition and post-transition + * ARM side handling e.g. calling WFI for the non-boot cpus for idle/sleep + * or either waiting for the go-ahead from the deep-sleep secure interrupt + * handler and then setting the romcode before entering wfi. The typical + * users of this routine are the CPUIdle and LDM suspend ie. pm_suspend(). + * The accelerators also need this service but currently this routine does + * not support it. This also assumes that the non-boot cpu's are in wfi + * and not wfe. + */ +/* FIXME : get these from platform/header files instead */ +/* GIC BAse Address */ +#define GIC_BASE_ADDR IO_ADDRESS(0xA0411000) + +/* ITs enabled for GIC. 104 is due to skipping of the STI and PPI sets. + * Rfer page 648 of the DB8500V1 spec v2.5 + */ +#define DIST_ENABLE_SET (GIC_BASE_ADDR + 0x104) +#define DIST_PENDING_SET (GIC_BASE_ADDR + 0x200) +#define DIST_ENABLE_CLEAR (GIC_BASE_ADDR + 0x180) +#define DIST_ACTIVE_BIT (GIC_BASE_ADDR + 0x304) + +#define PRCM_DEBUG_NOPWRDOWN_VAL IO_ADDRESS(0x80157194) +#define PRCM_POWER_STATE_VAL IO_ADDRESS(0x8015725C) + +int prcmu_apply_ap_state_transition(enum ap_pwrst_trans_t transition, + enum ddr_pwrst_t ddr_state_req, + int _4500_fifo_wakeup) +{ + int ret = 0; + int sync = 0; + unsigned int val = 0, timeout = 0, tmp ; + + if (u8500_is_earlydrop()) { + /* The PRCMU does do state transition validation, so we wl not + repeat it, just go ahead and call it */ + if (transition == APBOOT_TO_APEXECUTE_ED) + sync = 1; + + val = (ddr_state_req << 16) | (_4500_fifo_wakeup << 8) | \ + transition; + writel(val, PRCM_REQ_MB0_ED); + writel(1, PRCM_MBOX_CPU_SET); + + while ((readl(PRCM_MBOX_CPU_VAL) & 1) && sync) + cpu_relax(); + + switch (transition) { + + case APEXECUTE_TO_APSLEEP_ED: + case APEXECUTE_TO_APIDLE_ED: + __asm__ __volatile__("dsb\n\t" "wfi\n\t" \ + : : : "memory"); + break; + case APEXECUTE_TO_APDEEPSLEEP_ED: + printk(KERN_INFO "TODO: deep sleep \n"); + break; + case APBOOT_TO_APEXECUTE_ED: + break; + default: + ret = -EINVAL; + } + return ret; + } + + switch (transition) { + case APEXECUTE_TO_APSLEEP: + + /* PROGRAM THE ITMASK* registers for a Sleep */ + writel(0x00000000, PRCM_ARMITMSK31TO0); + writel(0x00000100, PRCM_ARMITMSK63TO32); + writel(0x00000000, PRCM_ARMITMSK95TO64); + writel(0x00000000, PRCM_ARMITMSK127TO96); + + /* probe the GIC if its already frozen */ + tmp = readl(PRCM_A9_MASK_ACK); + if (tmp & 0x00000001) + printk(KERN_WARNING "GIC is already frozen\n"); + + /* write to GIC freeze */ + tmp = readl(PRCM_A9_MASK_REQ); + tmp |= 0x01; + writel(tmp, PRCM_A9_MASK_REQ); + + /* we wait for a random timeout. as of now the + * the GIC freeze isnt ACKed; so wait + */ + timeout = 100; + while (timeout--) + cpu_relax(); + + /* PROGRAM WAKEUP EVENTS */ + /* write CfgWkUpsH in the Header */ + writeb(WKUPCFGH, PRCM_MBOX_HEADER_REQ_MB0); + + /* write to the mailbox */ + /* E8500Wakeup_ARMITMGMT Bit (1<<17). it is interpreted by + * the firmware to set the register prcm_armit_maskxp70_it + * (which is btw secure and thus only accessed by the xp70) + */ + writel((1<<17), PRCM_REQ_MB0_WKUP_8500); + writel(0x0, PRCM_REQ_MB0_WKUP_4500); + + /* SIGNAL MAILBOX */ + /* set the MBOX_CPU_SET bit to set an IT to xP70 */ + writel(1 << 0, PRCM_MBOX_CPU_SET); + + /* wait for corresponding MB0X_CPU_VAL bit to be cleared */ + while ((readl(PRCM_MBOX_CPU_VAL) & (1 << 0)) && timeout--) + cpu_relax(); + if (!timeout) { + printk(KERN_INFO + "Timeout in prcmu_configure_wakeup_events!!\n"); + return -EBUSY; + } + + /* CREATE MAILBOX FOR EXECUTE TO IDLE POWER TRANSITION */ + /* Write PwrStTrH=0 header to request a Power state xsition */ + writeb(0x0, PRCM_MBOX_HEADER_REQ_MB0); + + /* write request to the MBOX0 */ + writeb(APEXECUTE_TO_APSLEEP, PRCM_REQ_MB0_PWRSTTRH_APPWRST); + writeb(OFF, PRCM_REQ_MB0_PWRSTTRH_APPLLST); + writeb(ON, PRCM_REQ_MB0_PWRSTTRH_ULPCLKST); + writeb(0x56, PRCM_REQ_MB0_PWRSTTRH_BYTEFILL); + + /* As per the sync logic, we are supposed to be the final CPU. + * If the other CPU isnt in wfi, better exit by putting + * ourselves in wfi + */ + if (smp_processor_id()) { + if (!(readl(PRCM_ARM_WFI_STANDBY) & 0x8)) { + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return 0; + } + } else /* running on CPU0, check for CPU1 WFI standby */ { + if (!(readl(PRCM_ARM_WFI_STANDBY) & 0x10)) { + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return 0; + } + } + + /* Enable RTC-INT in the GIC */ + if (!(readl(PRCM_ARMITMSK31TO0) & 0x40000)) + writel((readl(PRCM_ARMITMSK31TO0) | 0x40000), + PRCM_ARMITMSK31TO0); + + + /* FIXME : later on, the ARM should not request a Idle if one + * of the ITSTATUS0/5 are still alive!!! + */ + if (readl(PRCM_ITSTATUS0) == 0x80) { + printk(KERN_WARNING "PRCM_ITSTATUS0 Not cleared\n"); + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return -EBUSY; + } + + /* + * + * REQUEST POWER XSITION + * + */ + + /* clear ACK MB0 */ + writeb(0x0, PRCM_ACK_MB0); + + /* trigger the XP70 IT10 to the XP70 */ + writel(1, PRCM_MBOX_CPU_SET); + + timeout = 200; + while (timeout--) + cpu_relax(); + + printk(KERN_INFO "u8500-prcm : To Deep Sleep\n"); + + /* Context Saving beings */ + + pm_save_config_PRCC(); + + pm_save_config_UART(); + + a9ss_save_public_config(); + + a9ss_save_scu_config(); + + /* TODO : take a backup of the SP and the public rom code + * entry point + */ + + /* here comes the wfi */ + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + + a9ss_restore_scu_config(); + + a9ss_restore_public_config(); + + /* Restore PRCC Configs */ + pm_restore_config_PRCC(); + + /* Restore UART Configs */ + pm_restore_config_UART(); + + if (readb(PRCM_ACK_MB0_AP_PWRST_STATUS) == 0xF3) + printk(KERN_INFO "u8500-prcm : Woken from Sleep OK\n"); + + break; + case APEXECUTE_TO_APIDLE: + /* Copy the current GIC set enable config as wakeup */ + for (val = 0; val < 4; val++) { + tmp = readl(DIST_ENABLE_SET + (val * 4)); + writel(tmp, PRCM_ARMITMSK31TO0 + (val * 4)); + } + + /* PROGRAM WAKEUP EVENTS */ + /* write CfgWkUpsH in the Header */ + writeb(WKUPCFGH, PRCM_MBOX_HEADER_REQ_MB0); + + /* write to the mailbox */ + /* E8500Wakeup_ARMITMGMT Bit (1<<17). it is interpreted by + * the firmware to set the register prcm_armit_maskxp70_it + * (which is btw secure and thus only accessed by the xp70) + */ + writel((1<<17), PRCM_REQ_MB0_WKUP_8500); + writel(0x0, PRCM_REQ_MB0_WKUP_4500); + _wait_for_req_complete(REQ_MB0); + +#if 0 + /* SIGNAL MAILBOX */ + /* set the MBOX_CPU_SET bit to set an IT to xP70 */ + writel(1 << 0, PRCM_MBOX_CPU_SET); + + /* wait for corresponding MB0X_CPU_VAL bit to be cleared */ + while ((readl(PRCM_MBOX_CPU_VAL) & (1 << 0)) && timeout--) + cpu_relax(); + if (!timeout) { + printk(KERN_INFO + "Timeout in prcmu_configure_wakeup_events!!\n"); + return -EBUSY; + } +#endif + /* CREATE MAILBOX FOR EXECUTE TO IDLE POWER TRANSITION */ + /* Write PwrStTrH=0 header to request a Power state xsition */ + writeb(0x0, PRCM_MBOX_HEADER_REQ_MB0); + + /* write request to the MBOX0 */ + writeb(APEXECUTE_TO_APIDLE, PRCM_REQ_MB0_PWRSTTRH_APPWRST); + writeb(ON, PRCM_REQ_MB0_PWRSTTRH_APPLLST); + writeb(ON, PRCM_REQ_MB0_PWRSTTRH_ULPCLKST); + writeb(0x55, PRCM_REQ_MB0_PWRSTTRH_BYTEFILL); + + /* As per the sync logic, we are supposed to be the final CPU. + * If the other CPU isnt in wfi, better exit by putting + * ourselves in wfi + */ + if (smp_processor_id()) { + if (!(readl(PRCM_ARM_WFI_STANDBY) & 0x8)) { + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return 0; + } + } else /* running on CPU0, check for CPU1 WFI standby */ { + if (!(readl(PRCM_ARM_WFI_STANDBY) & 0x10)) { + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return 0; + } + } + + /* FIXME : temporary hack to let UART2 interrupts enable the + * wake-up from ARM + */ + if (!(readl(PRCM_ARMITMSK31TO0) & 0x04000000)) + writel((readl(PRCM_ARMITMSK31TO0) | 0x04000000), + PRCM_ARMITMSK31TO0); + + /* FIXME : tempoeary hack to wake up from RTC */ + if (!(readl(PRCM_ARMITMSK31TO0) & 0x40000)) + writel((readl(PRCM_ARMITMSK31TO0) | 0x40000), + PRCM_ARMITMSK31TO0); + + /* FIXME : later on, the ARM should not request a Idle if one + * of the ITSTATUS0/5 are still alive!!! + */ + if (readl(PRCM_ITSTATUS0) == 0x80) { + printk(KERN_WARNING "PRCM_ITSTATUS0 Not cleared\n"); + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return -EBUSY; + } + + /* + * + * REQUEST POWER XSITION + * + */ + + /* clear ACK MB0 */ + writeb(0x0, PRCM_ACK_MB0); + + /* trigger the XP70 IT10 to the XP70 */ + writel(1, PRCM_MBOX_CPU_SET); + + /* here comes the wfi */ + __asm__ __volatile__("dsb\n\t" "wfi\n\t" : : : "memory"); + + writeb(RDWKUPACKH, PRCM_MBOX_HEADER_REQ_MB0); + + /* Set an interrupt to XP70 */ + writel(1 , PRCM_MBOX_CPU_SET); + while ((readl(PRCM_MBOX_CPU_VAL) & 1) && timeout--) + cpu_relax(); + if (!timeout) + return -EBUSY; + + break; + case APEXECUTE_TO_APDEEPSLEEP: + /* PROGRAM THE ITMASK* registers for deep sleep */ + writel(0x00000000, PRCM_ARMITMSK31TO0); + writel(0x00000100, PRCM_ARMITMSK63TO32); + writel(0x00000000, PRCM_ARMITMSK95TO64); + writel(0x00000000, PRCM_ARMITMSK127TO96); + + /* we skip the GIC freeze due to the FIQ being + * not handled by the ARM later on + */ + + /* PROGRAM WAKEUP EVENTS */ + /* write CfgWkUpsH in the Header */ + writeb(WKUPCFGH, PRCM_MBOX_HEADER_REQ_MB0); + + /* write to the mailbox */ + /* E8500Wakeup_ARMITMGMT Bit (1<<17). it is interpreted by + * the firmware to set the register prcm_armit_maskxp70_it + * (which is btw secure and thus only accessed by the xp70) + */ + writel((1<<17), PRCM_REQ_MB0_WKUP_8500); + writel(0x0, PRCM_REQ_MB0_WKUP_4500); + + /* SIGNAL MAILBOX */ + /* set the MBOX_CPU_SET bit to set an IT to xP70 */ + writel(1 << 0, PRCM_MBOX_CPU_SET); + + /* wait for corresponding MB0X_CPU_VAL bit to be cleared */ + while ((readl(PRCM_MBOX_CPU_VAL) & (1 << 0)) && timeout--) + cpu_relax(); + if (!timeout) { + printk(KERN_INFO + "Timeout in prcmu_configure_wakeup_events!!\n"); + return -EBUSY; + } + + /* CREATE MAILBOX FOR EXECUTE TO IDLE POWER TRANSITION */ + /* Write PwrStTrH=0 header to request a Power state xsition */ + writeb(0x0, PRCM_MBOX_HEADER_REQ_MB0); + + /* write request to the MBOX0 */ + writeb(APEXECUTE_TO_APDEEPSLEEP, PRCM_REQ_MB0_PWRSTTRH_APPWRST); + writeb(OFF, PRCM_REQ_MB0_PWRSTTRH_APPLLST); + writeb(ON, PRCM_REQ_MB0_PWRSTTRH_ULPCLKST); + writeb(0x56, PRCM_REQ_MB0_PWRSTTRH_BYTEFILL); + + /* As per the sync logic, we are supposed to be the final CPU. + * If the other CPU isnt in wfi, better exit by putting + * ourselves in wfi + */ + if (smp_processor_id()) { + if (!(readl(PRCM_ARM_WFI_STANDBY) & 0x8)) { + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return 0; + } + } else /* running on CPU0, check for CPU1 WFI standby */ { + if (!(readl(PRCM_ARM_WFI_STANDBY) & 0x10)) { + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return 0; + } + } + + /* Enable RTC-INT in the GIC */ + if (!(readl(PRCM_ARMITMSK31TO0) & 0x40000)) + writel((readl(PRCM_ARMITMSK31TO0) | 0x40000), + PRCM_ARMITMSK31TO0); + + + /* FIXME : later on, the ARM should not request a Idle if one + * of the ITSTATUS0/5 are still alive!!! + */ + if (readl(PRCM_ITSTATUS0) == 0x80) { + printk(KERN_WARNING "PRCM_ITSTATUS0 Not cleared\n"); + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + return -EBUSY; + } + + /* + * + * REQUEST POWER XSITION + * + */ + + /* clear ACK MB0 */ + writeb(0x0, PRCM_ACK_MB0); + + /* trigger the XP70 IT10 to the XP70 */ + writel(1, PRCM_MBOX_CPU_SET); + + timeout = 200; + while (timeout--) + cpu_relax(); + + printk(KERN_INFO "u8500-prcm : To Sleep\n"); + + /* Restore PRCC configs */ + pm_save_config_PRCC(); + + pm_save_config_UART(); + + a9ss_save_public_config(); + + /* here comes the wfi */ + __asm__ __volatile__( + "dsb\n\t" "wfi\n\t" : : : "memory"); + + /* Restore PRCC Configs */ + pm_restore_config_PRCC(); + + /* Restore UART Configs */ + pm_restore_config_UART(); + + if (readb(PRCM_ACK_MB0_AP_PWRST_STATUS) == 0xF3) + printk(KERN_INFO "u8500-prcm : Woken from Sleep OK\n"); + + break; + + printk(KERN_INFO "TODO: deep sleep \n"); + break; + case APBOOT_TO_APEXECUTE: + break; + default: + ret = -EINVAL; + } + return ret; + + +} +EXPORT_SYMBOL(prcmu_apply_ap_state_transition); + + + +/** + * prcmu_i2c_read - PRCMU - 4500 communication using PRCMU I2C + * @reg: - db8500 register bank to be accessed + * @slave: - db8500 register to be accessed + * Returns: ACK_MB5 value containing the status + */ +int prcmu_i2c_read(u8 reg, u8 slave) +{ + uint8_t i2c_status; + uint8_t i2c_val; + + dbg_printk("\nprcmu_4500_i2c_read: \ + bank=%x;reg=%x;\n", reg, slave); + + /* code related to FW 1_0_0_16 */ + writeb(I2CREAD, PRCM_REQ_MB5_I2COPTYPE); + writeb(reg, PRCM_REQ_MB5_I2CREG); + writeb(slave, PRCM_REQ_MB5_I2CSLAVE); + writeb(0, PRCM_REQ_MB5_I2CVAL); + + /* clear any previous ack */ + writel(0, PRCM_ACK_MB5); + + /* Set an interrupt to XP70 */ + writel(PRCM_XP70_TRIG_IT17, PRCM_MBOX_CPU_SET); + + dbg_printk("\n readl PRCM_ARM_IT1_VAL = %d \ + ", readb(PRCM_ARM_IT1_VAL)); + dbg_printk("\nwaiting at wait queue"); + + + /* wait for interrupt */ + wait_event_interruptible(ack_mb5_queue, \ + !(readl(PRCM_MBOX_CPU_VAL) & (PRCM_XP70_TRIG_IT17))); + + dbg_printk("\nAfter wait queue"); + + /* retrieve values */ + dbg_printk("ack-mb5:transfer status = %x\n", \ + readb(PRCM_ACK_MB5 + 0x1)); + dbg_printk("ack-mb5:reg_add = %x\n", readb(PRCM_ACK_MB5 + 0x2)); + dbg_printk("ack-mb5:slave_add = %x\n", \ + readb(PRCM_ACK_MB5 + 0x2)); + dbg_printk("ack-mb5:reg_val = %d\n", readb(PRCM_ACK_MB5 + 0x3)); + + + /* return ack_mb5.req_field.reg_val for a + req->req_field.I2C_op == I2C_READ */ + + i2c_status = readb(PRCM_ACK_MB5 + 0x1); + i2c_val = readb(PRCM_ACK_MB5 + 0x3); + + + if (i2c_status == I2C_RD_OK) + return i2c_val; + else { + + printk(KERN_INFO "prcmu_request_mailbox5:read return status = \ + %d\n", i2c_status); + return -EINVAL; + } + +} +EXPORT_SYMBOL(prcmu_i2c_read); + + +/** + * prcmu_i2c_write - PRCMU-db8500 communication using PRCMU I2C + * @reg: - db8500 register bank to be accessed + * @slave: - db800 register to be written to + * @reg_data: - the data to write + * Returns: ACK_MB5 value containing the status + */ +int prcmu_i2c_write(u8 reg, u8 slave, u8 reg_data) +{ + uint8_t i2c_status; + + /* NOTE : due to the I2C workaround, use + * MB5 for data, MB4 for the header + */ + + /* request I2C workaround header 0x0F */ + writeb(0x0F, PRCM_MBOX_HEADER_REQ_MB4); + + /* prepare the data for mailbox 5 */ + + /* register bank and I2C command */ + writeb((reg << 1) | I2CWRITE, PRCM_REQ_MB5 + 0x0); + /* APE_I2C comm. specifics */ + writeb((1 << 3) | 0x0, PRCM_REQ_MB5 + 0x1); + writeb(slave, PRCM_REQ_MB5_I2CSLAVE); + writeb(reg_data, PRCM_REQ_MB5_I2CVAL); + + /* we request the mailbox 4 */ + writel(PRCM_XP70_TRIG_IT14, PRCM_MBOX_CPU_SET); + + /* we wait for ACK mailbox 5 */ + /* FIXME : regularise this code with mailbox constants */ + while ((readl(PRCM_ARM_IT1_VAL) & (0x1 << 5)) != (0x1 << 5)) + cpu_relax(); + + /* we clear the ACK mailbox 5 interrupt */ + writel((0x1 << 5), PRCM_ARM_IT1_CLEAR); + + i2c_status = readb(PRCM_ACK_MB5 + 0x1); + if (i2c_status == I2C_WR_OK) + return I2C_WR_OK; + else { + printk(KERN_INFO "ape-i2c: i2c_status : 0x%x\n", i2c_status); + return -EINVAL; + } +} +EXPORT_SYMBOL(prcmu_i2c_write); + +int prcmu_i2c_get_status() +{ + return readb(PRCM_ACK_MB5_STATUS); +} +EXPORT_SYMBOL(prcmu_i2c_get_status); + +int prcmu_i2c_get_bank() +{ + return readb(PRCM_ACK_MB5_BANK); +} +EXPORT_SYMBOL(prcmu_i2c_get_bank); + +int prcmu_i2c_get_reg() +{ + return readb(PRCM_ACK_MB5_REG); +} +EXPORT_SYMBOL(prcmu_i2c_get_reg); + +int prcmu_i2c_get_val() +{ + return readb(PRCM_ACK_MB5_VAL); +} +EXPORT_SYMBOL(prcmu_i2c_get_val); + + + +/** + * prcmu_ac_wake_req - should be called whenever ARM wants to wakeup Modem + * + * Mailbox used : AckMb7 + * ACK : HostPortAck + */ +int prcmu_ac_wake_req() +{ + u32 prcm_hostaccess = readl(PRCM_HOSTACCESS_REQ); + prcm_hostaccess = prcm_hostaccess | ARM_WAKEUP_MODEM; + + /* 1. write to the PRCMU host_port_req register */ + writel(prcm_hostaccess, PRCM_HOSTACCESS_REQ); + + /* 2. wait for HostPortAck message in AckMb7 + wait_event_interruptible(ack_mb7_queue, (readl(PRCM_ARM_IT1_VAL) + & (1<<7))); + */ + + if (prcmu_read_ack_mb7() == HOST_PORT_ACK) + return 0; + else { + dbg_printk(KERN_INFO "\nprcmu_ac_wake_req: ack_mb7 \ + status = %x", prcmu_read_ack_mb7()); + return -EINVAL; + } + + + +} +EXPORT_SYMBOL(prcmu_ac_wake_req); + +/** + * prcmu_ac_sleep_req - called when ARM no longer needs to talk to modem + * + * Mailbox used - None + * ACK - None + */ +int prcmu_ac_sleep_req() +{ + /* clear the PRCMU host_port_req register */ + u32 orig_val = readl(PRCM_HOSTACCESS_REQ); + orig_val = orig_val & 0xFFFFFFFE; + + writel(orig_val, PRCM_HOSTACCESS_REQ); + + return 0; +} +EXPORT_SYMBOL(prcmu_ac_sleep_req); + + + +/** + * prcmu_configure_wakeup_events - configure 8500 and 4500 hw events on which + * PRCMU should wakeup AP + * + * Mailbox : PRCM_REQ_MB0 + * Header : WKUPCFGH + * ACK : None + */ +int prcmu_configure_wakeup_events(u32 event_8500_mask, \ + u32 event_4500_mask) +{ + int err = 0; + + /* write CfgWkUpsH in the Header */ + writeb(WKUPCFGH, PRCM_MBOX_HEADER_REQ_MB0); + + /* write to the mailbox */ + writel(event_8500_mask, PRCM_REQ_MB0_WKUP_8500); + writel(event_4500_mask, PRCM_REQ_MB0_WKUP_4500); + + err = _wait_for_req_complete(REQ_MB0); + + if (err) { + dbg_printk(KERN_INFO "\nTimeout configure_wakeup_events\n"); + } else { + dbg_printk(KERN_INFO "\nprcmu_configure_wakeup_events \ + done successfully!!\n"); + } + + /* No ack for this service. Directly return */ + return err; + + + +} +EXPORT_SYMBOL(prcmu_configure_wakeup_events); + + +/** + * prcmu_get_wakeup_reason - Retrieve the event which caused AP wakeup + * @event_8500: - corresponds to 8500 events + * @event_4500: - corresponds to 4500 events + * + * + * Mailbox : PRCM_ACK_MB0 + * Header : WKUPH + * ACK : None + */ +int prcmu_get_wakeup_reason(u32 *event_8500, u8 *event_4500 /* 20 bytes */) +{ + int i = 0; + + /* read the Rdp field */ + u8 rdp = readb(PRCM_ACK_MB0_PINGPONG_RDP); + + /* right now, some issues present in ping pong from fw side :) */ + /* read the event fields */ + + if (rdp) { + *event_8500 = readl(PRCM_ACK_MB0_WK1_EVENT_8500); + for (i = 0; i < PRCM_ACK_MB0_EVENT_4500_NUMBERS; i++) + event_4500[i] = readb(PRCM_ACK_MB0_WK1_EVENT_4500 + i); + } else { + *event_8500 = readl(PRCM_ACK_MB0_WK0_EVENT_8500); + for (i = 0; i < PRCM_ACK_MB0_EVENT_4500_NUMBERS; i++) + event_4500[i] = readb(PRCM_ACK_MB0_WK0_EVENT_4500 + i); + } + + /* No ack. Directly return */ + return 0; +} +EXPORT_SYMBOL(prcmu_get_wakeup_reason); + + +/** + * prcmu_ack_wakeup_reason - Arm acks to PRCMU that it read the wakeup reason + * + * + * Mailbox used : PRCM_ACK_MB0 + * Header used : RDWKUPACKH + * ACK : None + */ +int prcmu_ack_wakeup_reason() +{ + int err = 0; + + /* Acknowledge the wakeup events */ + writeb(RDWKUPACKH, PRCM_MBOX_HEADER_REQ_MB0); + err = _wait_for_req_complete(REQ_MB0); + + return err; +} +EXPORT_SYMBOL(prcmu_ack_wakeup_reason); + + +/** + * prcmu_is_ca_wake_req_pending - determine if ca_wake_req is pending + * + * Mailbox used - None + * Ack - None + * + * To be used by shrm driver to check if any ca_wake_req is pending + * initially and service the request accordingly. + */ +int prcmu_is_ca_wake_req_pending(void) +{ + int retval = ca_wake_req_pending; + if (ca_wake_req_pending) + ca_wake_req_pending--; + + return retval; +} +EXPORT_SYMBOL(prcmu_is_ca_wake_req_pending); + +/** + * prcmu_set_callback_cawakereq - callback of shrm for ca_wake_req + * @*func: Function pointer of shrm + * + * To call the registered shrm callback whenever ca_wake_req is got + */ +void prcmu_set_callback_cawakereq(void (*func)(u8)) +{ + prcmu_ca_wake_req_shrm_callback = func; +} +EXPORT_SYMBOL(prcmu_set_callback_cawakereq); + + +/** + * prcmu_set_callback_modem_reset_request - Set the callback function to call + * when modem requests for Reset + * + * Mailbox used - None + * Ack - None + */ +void prcmu_set_callback_modem_reset_request(void (*func)(void)) +{ + prcmu_modem_reset_shrm = func; +} +EXPORT_SYMBOL(prcmu_set_callback_modem_reset_request); + +/** + * prcmu_system_reset - System reset + * + * Sets the APE_SOFRST register which fires interrupt to fw + */ +void prcmu_system_reset(void) +{ + writel(1, PRCM_APE_SOFTRST); +} +EXPORT_SYMBOL(prcmu_system_reset); + +/** + * prcmu_read_ack_mb7 - Read the AckMb7 Status message + * + * + * Mailbox : AckMb7 + * Header : None + * ACK : None + * associated IT : prcm_arm_it1_val[7] + */ +int prcmu_read_ack_mb7() +{ + return readb(PRCM_ACK_MB7); +} +EXPORT_SYMBOL(prcmu_read_ack_mb7); + + +/** + * prcmu_ack_mb0_wkuph_status_tasklet - tasklet for ack mb0 IRQ + * @tasklet_data: tasklet data + * + * Tasklet for handling wakeup events given by IRQ corresponding to + * AckMb0 + */ +void prcmu_ack_mb0_wkuph_status_tasklet(unsigned long tasklet_data) +{ + uint32_t event_8500 = 0; + unsigned char event_4500[20]; + + prcmu_get_wakeup_reason(&event_8500, event_4500); + +#if 0 + dbg_printk("\n Inside prcmu_ack_mb0_wkuph_status_tasklet \n"); + dbg_printk("\n\nAcknowledging by RDWKUPACKH\n\n"); + prcmu_ack_wakeup_reason(); +#endif + + /* ca_wake_req signal - modem wakes up ARM */ + if (event_8500 & (1 << 5)) { + /* call shrm driver callback */ + if (prcmu_ca_wake_req_shrm_callback != NULL) + (*prcmu_ca_wake_req_shrm_callback)(1); + else { + printk(KERN_INFO "\n prcmu: SHRM callback for \ + ca_wake_req not registered!!\n"); + } + } +} + +/** + * prcmu_ack_mb7_status_tasklet - tasklet for mb7 IRQ + * @tasklet_data: tasklet data + * + * Tasklet for handling IPC communication related to AckMb7 + */ +void prcmu_ack_mb7_status_tasklet(unsigned long tasklet_data) +{ + /* read the ack mb7 value and carry out actions accordingly */ + u8 ack_mb7 = readb(PRCM_ACK_MB7); + + switch (ack_mb7) { + case MOD_SW_RESET_REQ: + /*forward the reset request to ARM */ + break; + case CA_SLEEP_REQ: + /* modem no longer requires to communicate + * with ARM so ARM can go to sleep */ + if (prcmu_ca_wake_req_shrm_callback != NULL) + (*prcmu_ca_wake_req_shrm_callback)(0); + else { + printk(KERN_INFO "\n prcmu: SHRM callback for \ + ca_wake_req not registered!!\n"); + } + break; + case HOST_PORT_ACK: + /* modem up.ARM can communicate to modem */ + /* wake_up_interruptible- prcmu_arm_wakeup_modem api*/ + wake_up_interruptible(&ack_mb7_queue); + break; + }; + +} + +/** + * prcmu_ack_mbox_irq_handler - IRQ handler for Ack mailboxes + * @irq: the irq number + * + * IRQ Handler for the Ack Mailboxes + * Find out the arm_it1_val bit and carry out the tasks + * accordingly + */ +irqreturn_t prcmu_ack_mbox_irq_handler(int irq, void *ctrlr) +{ + uint8_t prcm_arm_it1_val = 0; + + dbg_printk("\nInside prcmu_ack_mbox_irq_handler !!\n"); + + /* check the prcm_arm_it1_val register to find which ACK mailbox is + * filled by PRCMU fw */ + prcm_arm_it1_val = readb(PRCM_ARM_IT1_VAL); + + dbg_printk(" prcm_arm_it1_val = %d ", prcm_arm_it1_val); + + if (prcm_arm_it1_val & (1 << 0)) { + dbg_printk("\n Inside IRQ handler for Ack mb0 "); + wake_up_interruptible(&ack_mb0_queue); + + dbg_printk("\n readb(PRCM_MBOX_HEADER_ACK_MB0) = %x", \ + readb(PRCM_MBOX_HEADER_ACK_MB0)); + + /*read the header */ + if ((readb(PRCM_MBOX_HEADER_ACK_MB0)) == PWRSTTRH) { + /* call the callback for AckMb0_PWRSTTRH */ + /*tasklet_schedule(&prcmu_ack_mb0_pwrst_tasklet);*/ + /* call wake_up_event_interruptible for mb0 */ + dbg_printk("\n Inside IRQ handler for Ack mb0 \ + PWRSTTRH and waking up "); + + } else if ((readb(PRCM_MBOX_HEADER_ACK_MB0)) == WKUPH) { + dbg_printk("\n IRQ handler for Ack mb0 WKUPH "); + tasklet_schedule(&prcmu_ack_mb0_wkuph_tasklet); + + } + } else if (prcm_arm_it1_val & (1<<1)) + dbg_printk("\n IRQ handler for Ack mb1\n"); + else if (prcm_arm_it1_val & (1<<2)) + dbg_printk("\n IRQ handler for Ack mb2\n"); + else if (prcm_arm_it1_val & (1<<3)) + dbg_printk("\n IRQ handler for Ack mb3\n"); + else if (prcm_arm_it1_val & (1<<4)) + dbg_printk("\n IRQ handler for Ack mb4\n"); + else if (prcm_arm_it1_val & (1<<5)) { + /* No header reading required */ + /* call wake_up_event_interruptible for mb5 transaction */ + dbg_printk("\nInside prcmu IRQ handler for mb5 "); + wake_up_interruptible(&ack_mb5_queue); + } else if (prcm_arm_it1_val & (1<<6)) + dbg_printk("\n IRQ handler for Ack mb6\n"); + else if (prcm_arm_it1_val & (1<<7)) { + /* No header reading required */ + dbg_printk("\n IRQ handler for Ack mb7\n"); + tasklet_schedule(&prcmu_ack_mb7_tasklet); + } + + /* clear arm_it1_val bits */ + writeb(255, PRCM_ARM_IT1_CLEAR); + return IRQ_HANDLED; +} + + +/** + * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic + * + */ +static int prcmu_fw_init(void) +{ + int err = 0; + /* configure the wake-up events */ + u32 event_8500 = 0x0; + u32 event_4500 = 0x0; + int prcm_arm_it1_val = 0; + + if (u8500_is_earlydrop()) { + int i; + int status = prcmu_get_boot_status(); + if (status != 0xFF || status != 0x2F) { + printk("PRCMU Firmware not ready\n"); + return -EIO; + } + /* This can be enabled once PRCMU fw flashing is default */ + prcmu_apply_ap_state_transition(APBOOT_TO_APEXECUTE_ED, + DDR_PWR_STATE_UNCHANGED_ED, 0); + + /* for the time being assign wake-up duty to all interrupts */ + /* Enable all interrupts as wake-up source */ + for (i = 0; i < 128; i++) + prcmu_set_irq_wakeup(i); + + return 0; + } + + /* configure the wakeup events */ + event_8500 = (1 << 5); + event_4500 = 0x0; + prcmu_configure_wakeup_events(event_8500, event_4500); + dbg_printk("(WkUpCfgOk=0xEA)PRCM_ACK_MB0_AP_PWRST_STATUS = %x\n", + readb(PRCM_ACK_MB0_AP_PWRST_STATUS)); + + /* retrieve the current interrupt status from PRCMU FW */ + prcm_arm_it1_val = readb(PRCM_ARM_IT1_VAL); + if (prcm_arm_it1_val && (1 << 0)) { + /* clear the arm_it1_val to low the IT#47 */ + writeb(0xFF, PRCM_ARM_IT1_CLEAR); + + /* init irqs */ + err = request_irq(IRQ_PRCM_ACK_MBOX, + prcmu_ack_mbox_irq_handler, IRQF_TRIGGER_RISING, + "prcmu_ack_mbox", NULL); + if (err < 0) { + printk(KERN_ERR "\nFailed to allocate \ + IRQ_PRCM_ACK_MBOX!!\n"); + err = -EBUSY; + free_irq(IRQ_PRCM_ACK_MBOX, NULL); + goto err_return; + } + + /* check for any existing wakeup events */ + if (readb(PRCM_MBOX_HEADER_ACK_MB0) == WKUPH) { + /* check here for the wakeup source for cawakereq + debugging on-going with fw for ping-pong. + Currently, its not possible to read source of + wakeup event correctly from fw mailbox */ + + /* increment pending flag for the shrm driver + to check */ + ca_wake_req_pending++; + + /* acknowledge reading the wakeup reason to fw */ + prcmu_ack_wakeup_reason(); + } + } + + + if (prcmu_get_xp70_current_state() == AP_BOOT) + prcmu_apply_ap_state_transition(APBOOT_TO_APEXECUTE, \ + DDR_PWR_STATE_UNCHANGED, 0); + else if (prcmu_get_xp70_current_state() == AP_EXECUTE) { + printk(KERN_INFO "PRCMU firmware booted.\n"); + } else { + printk(KERN_WARNING "WARNING - PRCMU firmware not yet booted!!!\n"); + return -ENODEV; + } + +err_return: + return err; +} + +arch_initcall(prcmu_fw_init); |