|
@@ -343,11 +343,13 @@ static struct {
|
|
|
* mb1_transfer - state needed for mailbox 1 communication.
|
|
|
* @lock: The transaction lock.
|
|
|
* @work: The transaction completion structure.
|
|
|
+ * @ape_opp: The current APE OPP.
|
|
|
* @ack: Reply ("acknowledge") data.
|
|
|
*/
|
|
|
static struct {
|
|
|
struct mutex lock;
|
|
|
struct completion work;
|
|
|
+ u8 ape_opp;
|
|
|
struct {
|
|
|
u8 header;
|
|
|
u8 arm_opp;
|
|
@@ -816,6 +818,11 @@ int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+u8 db8500_prcmu_get_power_state_result(void)
|
|
|
+{
|
|
|
+ return readb(tcdm_base + PRCM_ACK_MB0_AP_PWRSTTR_STATUS);
|
|
|
+}
|
|
|
+
|
|
|
/* This function should only be called while mb0_transfer.lock is held. */
|
|
|
static void config_wakeups(void)
|
|
|
{
|
|
@@ -965,6 +972,52 @@ int db8500_prcmu_set_ddr_opp(u8 opp)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/* Divide the frequency of certain clocks by 2 for APE_50_PARTLY_25_OPP. */
|
|
|
+static void request_even_slower_clocks(bool enable)
|
|
|
+{
|
|
|
+ void __iomem *clock_reg[] = {
|
|
|
+ PRCM_ACLK_MGT,
|
|
|
+ PRCM_DMACLK_MGT
|
|
|
+ };
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&clk_mgt_lock, flags);
|
|
|
+
|
|
|
+ /* Grab the HW semaphore. */
|
|
|
+ while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0)
|
|
|
+ cpu_relax();
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(clock_reg); i++) {
|
|
|
+ u32 val;
|
|
|
+ u32 div;
|
|
|
+
|
|
|
+ val = readl(clock_reg[i]);
|
|
|
+ div = (val & PRCM_CLK_MGT_CLKPLLDIV_MASK);
|
|
|
+ if (enable) {
|
|
|
+ if ((div <= 1) || (div > 15)) {
|
|
|
+ pr_err("prcmu: Bad clock divider %d in %s\n",
|
|
|
+ div, __func__);
|
|
|
+ goto unlock_and_return;
|
|
|
+ }
|
|
|
+ div <<= 1;
|
|
|
+ } else {
|
|
|
+ if (div <= 2)
|
|
|
+ goto unlock_and_return;
|
|
|
+ div >>= 1;
|
|
|
+ }
|
|
|
+ val = ((val & ~PRCM_CLK_MGT_CLKPLLDIV_MASK) |
|
|
|
+ (div & PRCM_CLK_MGT_CLKPLLDIV_MASK));
|
|
|
+ writel(val, clock_reg[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+unlock_and_return:
|
|
|
+ /* Release the HW semaphore. */
|
|
|
+ writel(0, PRCM_SEM);
|
|
|
+
|
|
|
+ spin_unlock_irqrestore(&clk_mgt_lock, flags);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* db8500_set_ape_opp - set the appropriate APE OPP
|
|
|
* @opp: The new APE operating point to which transition is to be made
|
|
@@ -976,14 +1029,24 @@ int db8500_prcmu_set_ape_opp(u8 opp)
|
|
|
{
|
|
|
int r = 0;
|
|
|
|
|
|
+ if (opp == mb1_transfer.ape_opp)
|
|
|
+ return 0;
|
|
|
+
|
|
|
mutex_lock(&mb1_transfer.lock);
|
|
|
|
|
|
+ if (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP)
|
|
|
+ request_even_slower_clocks(false);
|
|
|
+
|
|
|
+ if ((opp != APE_100_OPP) && (mb1_transfer.ape_opp != APE_100_OPP))
|
|
|
+ goto skip_message;
|
|
|
+
|
|
|
while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1))
|
|
|
cpu_relax();
|
|
|
|
|
|
writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1));
|
|
|
writeb(ARM_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_ARM_OPP));
|
|
|
- writeb(opp, (tcdm_base + PRCM_REQ_MB1_APE_OPP));
|
|
|
+ writeb(((opp == APE_50_PARTLY_25_OPP) ? APE_50_OPP : opp),
|
|
|
+ (tcdm_base + PRCM_REQ_MB1_APE_OPP));
|
|
|
|
|
|
writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET);
|
|
|
wait_for_completion(&mb1_transfer.work);
|
|
@@ -992,6 +1055,13 @@ int db8500_prcmu_set_ape_opp(u8 opp)
|
|
|
(mb1_transfer.ack.ape_opp != opp))
|
|
|
r = -EIO;
|
|
|
|
|
|
+skip_message:
|
|
|
+ if ((!r && (opp == APE_50_PARTLY_25_OPP)) ||
|
|
|
+ (r && (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP)))
|
|
|
+ request_even_slower_clocks(true);
|
|
|
+ if (!r)
|
|
|
+ mb1_transfer.ape_opp = opp;
|
|
|
+
|
|
|
mutex_unlock(&mb1_transfer.lock);
|
|
|
|
|
|
return r;
|
|
@@ -2631,6 +2701,7 @@ void __init db8500_prcmu_early_init(void)
|
|
|
init_completion(&mb0_transfer.ac_wake_work);
|
|
|
mutex_init(&mb1_transfer.lock);
|
|
|
init_completion(&mb1_transfer.work);
|
|
|
+ mb1_transfer.ape_opp = APE_NO_CHANGE;
|
|
|
mutex_init(&mb2_transfer.lock);
|
|
|
init_completion(&mb2_transfer.work);
|
|
|
spin_lock_init(&mb2_transfer.auto_pm_lock);
|