/* * tiomap_pwr.c * * DSP-BIOS Bridge driver support functions for TI OMAP processors. * * Implementation of DSP wake/sleep routines. * * Copyright (C) 2007-2008 Texas Instruments, Inc. * * This package 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. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* ----------------------------------- Host OS */ #include #include /* ----------------------------------- DSP/BIOS Bridge */ #include #include #include /* ----------------------------------- Platform Manager */ #include #include #include /* ------------------------------------ Hardware Abstraction Layer */ #include #include #include /* ----------------------------------- Bridge Driver */ #include #include /* ----------------------------------- specific to this file */ #include "_tiomap.h" #include "_tiomap_pwr.h" #include #include #define PWRSTST_TIMEOUT 200 /* * ======== handle_constraints_set ======== * Sets new DSP constraint */ int handle_constraints_set(struct bridge_dev_context *dev_context, void *pargs) { #ifdef CONFIG_TIDSPBRIDGE_DVFS u32 *constraint_val; struct omap_dsp_platform_data *pdata = omap_dspbridge_dev->dev.platform_data; constraint_val = (u32 *) (pargs); /* Read the target value requested by DSP */ dev_dbg(bridge, "OPP: %s opp requested = 0x%x\n", __func__, (u32) *(constraint_val + 1)); /* Set the new opp value */ if (pdata->dsp_set_min_opp) (*pdata->dsp_set_min_opp) ((u32) *(constraint_val + 1)); #endif /* #ifdef CONFIG_TIDSPBRIDGE_DVFS */ return 0; } /* * ======== handle_hibernation_from_dsp ======== * Handle Hibernation requested from DSP */ int handle_hibernation_from_dsp(struct bridge_dev_context *dev_context) { int status = 0; #ifdef CONFIG_PM u16 timeout = PWRSTST_TIMEOUT / 10; u32 pwr_state; #ifdef CONFIG_TIDSPBRIDGE_DVFS u32 opplevel; struct io_mgr *hio_mgr; #endif struct omap_dsp_platform_data *pdata = omap_dspbridge_dev->dev.platform_data; pwr_state = (*pdata->dsp_prm_read)(OMAP3430_IVA2_MOD, OMAP2_PM_PWSTST) & OMAP_POWERSTATEST_MASK; /* Wait for DSP to move into OFF state */ while ((pwr_state != PWRDM_POWER_OFF) && --timeout) { if (msleep_interruptible(10)) { pr_err("Waiting for DSP OFF mode interrupted\n"); return -EPERM; } pwr_state = (*pdata->dsp_prm_read)(OMAP3430_IVA2_MOD, OMAP2_PM_PWSTST) & OMAP_POWERSTATEST_MASK; } if (timeout == 0) { pr_err("%s: Timed out waiting for DSP off mode\n", __func__); status = -ETIMEDOUT; return status; } else { /* Save mailbox settings */ omap_mbox_save_ctx(dev_context->mbox); /* Turn off DSP Peripheral clocks and DSP Load monitor timer */ status = dsp_clock_disable_all(dev_context->dsp_per_clks); /* Disable wdt on hibernation. */ dsp_wdt_enable(false); if (!status) { /* Update the Bridger Driver state */ dev_context->brd_state = BRD_DSP_HIBERNATION; #ifdef CONFIG_TIDSPBRIDGE_DVFS status = dev_get_io_mgr(dev_context->dev_obj, &hio_mgr); if (!hio_mgr) { status = DSP_EHANDLE; return status; } io_sh_msetting(hio_mgr, SHM_GETOPP, &opplevel); /* * Set the OPP to low level before moving to OFF * mode */ if (pdata->dsp_set_min_opp) (*pdata->dsp_set_min_opp) (VDD1_OPP1); status = 0; #endif /* CONFIG_TIDSPBRIDGE_DVFS */ } } #endif return status; } /* * ======== sleep_dsp ======== * Put DSP in low power consuming state. */ int sleep_dsp(struct bridge_dev_context *dev_context, u32 dw_cmd, void *pargs) { int status = 0; #ifdef CONFIG_PM #ifdef CONFIG_TIDSPBRIDGE_NTFY_PWRERR struct deh_mgr *hdeh_mgr; #endif /* CONFIG_TIDSPBRIDGE_NTFY_PWRERR */ u16 timeout = PWRSTST_TIMEOUT / 10; u32 pwr_state, target_pwr_state; struct omap_dsp_platform_data *pdata = omap_dspbridge_dev->dev.platform_data; /* Check if sleep code is valid */ if ((dw_cmd != PWR_DEEPSLEEP) && (dw_cmd != PWR_EMERGENCYDEEPSLEEP)) return -EINVAL; switch (dev_context->brd_state) { case BRD_RUNNING: omap_mbox_save_ctx(dev_context->mbox); if (dsp_test_sleepstate == PWRDM_POWER_OFF) { sm_interrupt_dsp(dev_context, MBX_PM_DSPHIBERNATE); dev_dbg(bridge, "PM: %s - sent hibernate cmd to DSP\n", __func__); target_pwr_state = PWRDM_POWER_OFF; } else { sm_interrupt_dsp(dev_context, MBX_PM_DSPRETENTION); target_pwr_state = PWRDM_POWER_RET; } break; case BRD_RETENTION: omap_mbox_save_ctx(dev_context->mbox); if (dsp_test_sleepstate == PWRDM_POWER_OFF) { sm_interrupt_dsp(dev_context, MBX_PM_DSPHIBERNATE); target_pwr_state = PWRDM_POWER_OFF; } else return 0; break; case BRD_HIBERNATION: case BRD_DSP_HIBERNATION: /* Already in Hibernation, so just return */ dev_dbg(bridge, "PM: %s - DSP already in hibernation\n", __func__); return 0; case BRD_STOPPED: dev_dbg(bridge, "PM: %s - Board in STOP state\n", __func__); return 0; default: dev_dbg(bridge, "PM: %s - Bridge in Illegal state\n", __func__); return -EPERM; } /* Get the PRCM DSP power domain status */ pwr_state = (*pdata->dsp_prm_read)(OMAP3430_IVA2_MOD, OMAP2_PM_PWSTST) & OMAP_POWERSTATEST_MASK; /* Wait for DSP to move into target power state */ while ((pwr_state != target_pwr_state) && --timeout) { if (msleep_interruptible(10)) { pr_err("Waiting for DSP to Suspend interrupted\n"); return -EPERM; } pwr_state = (*pdata->dsp_prm_read)(OMAP3430_IVA2_MOD, OMAP2_PM_PWSTST) & OMAP_POWERSTATEST_MASK; } if (!timeout) { pr_err("%s: Timed out waiting for DSP off mode, state %x\n", __func__, pwr_state); #ifdef CONFIG_TIDSPBRIDGE_NTFY_PWRERR dev_get_deh_mgr(dev_context->dev_obj, &hdeh_mgr); bridge_deh_notify(hdeh_mgr, DSP_PWRERROR, 0); #endif /* CONFIG_TIDSPBRIDGE_NTFY_PWRERR */ return -ETIMEDOUT; } else { /* Update the Bridger Driver state */ if (dsp_test_sleepstate == PWRDM_POWER_OFF) dev_context->brd_state = BRD_HIBERNATION; else dev_context->brd_state = BRD_RETENTION; /* Disable wdt on hibernation. */ dsp_wdt_enable(false); /* Turn off DSP Peripheral clocks */ status = dsp_clock_disable_all(dev_context->dsp_per_clks); if (status) return status; #ifdef CONFIG_TIDSPBRIDGE_DVFS else if (target_pwr_state == PWRDM_POWER_OFF) { /* * Set the OPP to low level before moving to OFF mode */ if (pdata->dsp_set_min_opp) (*pdata->dsp_set_min_opp) (VDD1_OPP1); } #endif /* CONFIG_TIDSPBRIDGE_DVFS */ } #endif /* CONFIG_PM */ return status; } /* * ======== wake_dsp ======== * Wake up DSP from sleep. */ int wake_dsp(struct bridge_dev_context *dev_context, void *pargs) { int status = 0; #ifdef CONFIG_PM /* Check the board state, if it is not 'SLEEP' then return */ if (dev_context->brd_state == BRD_RUNNING || dev_context->brd_state == BRD_STOPPED) { /* The Device is in 'RET' or 'OFF' state and Bridge state is not * 'SLEEP', this means state inconsistency, so return */ return 0; } /* Send a wakeup message to DSP */ sm_interrupt_dsp(dev_context, MBX_PM_DSPWAKEUP); /* Set the device state to RUNNIG */ dev_context->brd_state = BRD_RUNNING; #endif /* CONFIG_PM */ return status; } /* * ======== dsp_peripheral_clk_ctrl ======== * Enable/Disable the DSP peripheral clocks as needed.. */ int dsp_peripheral_clk_ctrl(struct bridge_dev_context *dev_context, void *pargs) { u32 ext_clk = 0; u32 ext_clk_id = 0; u32 ext_clk_cmd = 0; u32 clk_id_index = MBX_PM_MAX_RESOURCES; u32 tmp_index; u32 dsp_per_clks_before; int status = 0; dsp_per_clks_before = dev_context->dsp_per_clks; ext_clk = (u32) *((u32 *) pargs); ext_clk_id = ext_clk & MBX_PM_CLK_IDMASK; /* process the power message -- TODO, keep it in a separate function */ for (tmp_index = 0; tmp_index < MBX_PM_MAX_RESOURCES; tmp_index++) { if (ext_clk_id == bpwr_clkid[tmp_index]) { clk_id_index = tmp_index; break; } } /* TODO -- Assert may be a too hard restriction here.. May be we should * just return with failure when the CLK ID does not match */ /* DBC_ASSERT(clk_id_index < MBX_PM_MAX_RESOURCES); */ if (clk_id_index == MBX_PM_MAX_RESOURCES) { /* return with a more meaningfull error code */ return -EPERM; } ext_clk_cmd = (ext_clk >> MBX_PM_CLK_CMDSHIFT) & MBX_PM_CLK_CMDMASK; switch (ext_clk_cmd) { case BPWR_DISABLE_CLOCK: status = dsp_clk_disable(bpwr_clks[clk_id_index].clk); dsp_clk_wakeup_event_ctrl(bpwr_clks[clk_id_index].clk_id, false); if (!status) { (dev_context->dsp_per_clks) &= (~((u32) (1 << bpwr_clks[clk_id_index].clk))); } break; case BPWR_ENABLE_CLOCK: status = dsp_clk_enable(bpwr_clks[clk_id_index].clk); dsp_clk_wakeup_event_ctrl(bpwr_clks[clk_id_index].clk_id, true); if (!status) (dev_context->dsp_per_clks) |= (1 << bpwr_clks[clk_id_index].clk); break; default: dev_dbg(bridge, "%s: Unsupported CMD\n", __func__); /* unsupported cmd */ /* TODO -- provide support for AUTOIDLE Enable/Disable * commands */ } return status; } /* * ========pre_scale_dsp======== * Sends prescale notification to DSP * */ int pre_scale_dsp(struct bridge_dev_context *dev_context, void *pargs) { #ifdef CONFIG_TIDSPBRIDGE_DVFS u32 level; u32 voltage_domain; voltage_domain = *((u32 *) pargs); level = *((u32 *) pargs + 1); dev_dbg(bridge, "OPP: %s voltage_domain = %x, level = 0x%x\n", __func__, voltage_domain, level); if ((dev_context->brd_state == BRD_HIBERNATION) || (dev_context->brd_state == BRD_RETENTION) || (dev_context->brd_state == BRD_DSP_HIBERNATION)) { dev_dbg(bridge, "OPP: %s IVA in sleep. No message to DSP\n"); return 0; } else if ((dev_context->brd_state == BRD_RUNNING)) { /* Send a prenotificatio to DSP */ dev_dbg(bridge, "OPP: %s sent notification to DSP\n", __func__); sm_interrupt_dsp(dev_context, MBX_PM_SETPOINT_PRENOTIFY); return 0; } else { return -EPERM; } #endif /* #ifdef CONFIG_TIDSPBRIDGE_DVFS */ return 0; } /* * ========post_scale_dsp======== * Sends postscale notification to DSP * */ int post_scale_dsp(struct bridge_dev_context *dev_context, void *pargs) { int status = 0; #ifdef CONFIG_TIDSPBRIDGE_DVFS u32 level; u32 voltage_domain; struct io_mgr *hio_mgr; status = dev_get_io_mgr(dev_context->dev_obj, &hio_mgr); if (!hio_mgr) return -EFAULT; voltage_domain = *((u32 *) pargs); level = *((u32 *) pargs + 1); dev_dbg(bridge, "OPP: %s voltage_domain = %x, level = 0x%x\n", __func__, voltage_domain, level); if ((dev_context->brd_state == BRD_HIBERNATION) || (dev_context->brd_state == BRD_RETENTION) || (dev_context->brd_state == BRD_DSP_HIBERNATION)) { /* Update the OPP value in shared memory */ io_sh_msetting(hio_mgr, SHM_CURROPP, &level); dev_dbg(bridge, "OPP: %s IVA in sleep. Wrote to shm\n", __func__); } else if ((dev_context->brd_state == BRD_RUNNING)) { /* Update the OPP value in shared memory */ io_sh_msetting(hio_mgr, SHM_CURROPP, &level); /* Send a post notification to DSP */ sm_interrupt_dsp(dev_context, MBX_PM_SETPOINT_POSTNOTIFY); dev_dbg(bridge, "OPP: %s wrote to shm. Sent post notification " "to DSP\n", __func__); } else { status = -EPERM; } #endif /* #ifdef CONFIG_TIDSPBRIDGE_DVFS */ return status; } void dsp_clk_wakeup_event_ctrl(u32 clock_id, bool enable) { struct cfg_hostres *resources; int status = 0; u32 iva2_grpsel; u32 mpu_grpsel; struct dev_object *hdev_object = NULL; struct bridge_dev_context *bridge_context = NULL; hdev_object = (struct dev_object *)drv_get_first_dev_object(); if (!hdev_object) return; status = dev_get_bridge_context(hdev_object, &bridge_context); if (!bridge_context) return; resources = bridge_context->resources; if (!resources) return; switch (clock_id) { case BPWR_GP_TIMER5: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_GPT5_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_GPT5_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_GPT5_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_GPT5_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; case BPWR_GP_TIMER6: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_GPT6_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_GPT6_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_GPT6_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_GPT6_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; case BPWR_GP_TIMER7: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_GPT7_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_GPT7_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_GPT7_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_GPT7_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; case BPWR_GP_TIMER8: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_GPT8_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_GPT8_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_GPT8_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_GPT8_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; case BPWR_MCBSP1: iva2_grpsel = readl(resources->core_pm_base + 0xA8); mpu_grpsel = readl(resources->core_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_MCBSP1_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_MCBSP1_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_MCBSP1_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_MCBSP1_MASK; } writel(iva2_grpsel, resources->core_pm_base + 0xA8); writel(mpu_grpsel, resources->core_pm_base + 0xA4); break; case BPWR_MCBSP2: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_MCBSP2_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_MCBSP2_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_MCBSP2_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_MCBSP2_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; case BPWR_MCBSP3: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_MCBSP3_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_MCBSP3_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_MCBSP3_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_MCBSP3_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; case BPWR_MCBSP4: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_MCBSP4_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_MCBSP4_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_MCBSP4_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_MCBSP4_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; case BPWR_MCBSP5: iva2_grpsel = readl(resources->per_pm_base + 0xA8); mpu_grpsel = readl(resources->per_pm_base + 0xA4); if (enable) { iva2_grpsel |= OMAP3430_GRPSEL_MCBSP5_MASK; mpu_grpsel &= ~OMAP3430_GRPSEL_MCBSP5_MASK; } else { mpu_grpsel |= OMAP3430_GRPSEL_MCBSP5_MASK; iva2_grpsel &= ~OMAP3430_GRPSEL_MCBSP5_MASK; } writel(iva2_grpsel, resources->per_pm_base + 0xA8); writel(mpu_grpsel, resources->per_pm_base + 0xA4); break; } }