/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2010 by Michael Sevakis * * i.MX31 DVFS and DPTC drivers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "config.h" #include "system.h" #include "logf.h" #include "mc13783.h" #include "iomuxc-imx31.h" #include "ccm-imx31.h" #include "avic-imx31.h" #include "dvfs_dptc-imx31.h" #include "dvfs_dptc_tables-target.h" #include "gcc_extensions.h" /* Most of the code in here is based upon the Linux BSP provided by Freescale * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. */ /* The current DVFS index level */ static volatile unsigned int dvfs_level = DVFS_LEVEL_DEFAULT; /* The current DPTC working point */ static volatile unsigned int dptc_wp = DPTC_WP_DEFAULT; /* Synchronize DPTC comparator value registers to new table row */ static void update_dptc_counts(void) { const struct dptc_dcvr_table_entry * const entry = &dptc_dcvr_table[dvfs_level][dptc_wp]; CCM_DCVR0 = entry->dcvr0; CCM_DCVR1 = entry->dcvr1; CCM_DCVR2 = entry->dcvr2; CCM_DCVR3 = entry->dcvr3; } /* Enable DPTC and unmask interrupt. */ static void enable_dptc(void) { /* Enable DPTC, assert voltage change request. */ CCM_PMCR0 = (CCM_PMCR0 & ~CCM_PMCR0_PTVAIM) | CCM_PMCR0_DPTEN | CCM_PMCR0_DPVCR; udelay(2); /* Now set that voltage is valid */ CCM_PMCR0 |= CCM_PMCR0_DPVV; } static uint32_t check_regulator_setting(uint32_t setting) { /* Simply a safety check *in case* table gets scrambled */ if (setting < VOLTAGE_SETTING_MIN) setting = VOLTAGE_SETTING_MIN; else if (setting > VOLTAGE_SETTING_MAX) setting = VOLTAGE_SETTING_MAX; return setting; } /** DVFS **/ #define DVFS_TVWAIT 100 /* Voltage ramp wait time */ static bool dvfs_running = false; /* Has driver enabled DVFS? */ /* Request tracking since boot */ unsigned int dvfs_nr_dn = 0; unsigned int dvfs_nr_up = 0; unsigned int dvfs_nr_pnc = 0; unsigned int dvfs_nr_no = 0; /* Wait for the UPDTEN flag to be set so that all bits may be written */ static inline void updten_wait(void) { while (!(CCM_PMCR0 & CCM_PMCR0_UPDTEN)); } /* Do the actual frequency and DVFS pin change - always call with IRQ masked */ static void do_dvfs_update(unsigned long pmcr0, unsigned int level) { const struct dvfs_clock_table_entry *setting = &dvfs_clock_table[level]; if (pmcr0 & CCM_PMCR0_DPTEN) { /* Ignore voltage change request from DPTC. Voltage is *not* valid. */ pmcr0 &= ~CCM_PMCR0_DPVCR; /* Mask DPTC interrupt for when called in thread context */ pmcr0 |= CCM_PMCR0_PTVAIM; } pmcr0 &= ~CCM_PMCR0_VSCNT; if (level < ((pmcr0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS)) { pmcr0 |= CCM_PMCR0_UDSC; /* Up scaling, increase */ pmcr0 |= setting->vscnt << CCM_PMCR0_VSCNT_POS; } else { pmcr0 &= ~CCM_PMCR0_UDSC; /* Down scaling, decrease */ pmcr0 |= 0x1 << CCM_PMCR0_VSCNT_POS; } /* DVSUP (new frequency index) setup */ pmcr0 = (pmcr0 & ~CCM_PMCR0_DVSUP) | (level << CCM_PMCR0_DVSUP_POS); /* Save new level */ dvfs_level = level; if ((setting->pll_num << CCM_PMCR0_DFSUP_MCUPLL_POS) ^ (pmcr0 & CCM_PMCR0_DFSUP_MCUPLL)) { /* Update pll and post-dividers. */ pmcr0 ^= CCM_PMCR0_DFSUP_MCUPLL; pmcr0 &= ~CCM_PMCR0_DFSUP_POST_DIVIDERS; } else { /* Post-dividers update only */ pmcr0 |= CCM_PMCR0_DFSUP_POST_DIVIDERS; } CCM_PMCR0 = pmcr0; /* dvfs_int_voltage_wait_complete must be call to complete this; how that is accomplished depends upon whether this was an interrupt with DVFS enabled or a manual setting of the CPU frequency */ } /* Perform final DVFS frequency change steps after voltage ramp wait */ static void dvfs_int_voltage_wait_complete(void) { const struct dvfs_clock_table_entry *setting = &dvfs_clock_table[dvfs_level]; unsigned long pmcr0 = CCM_PMCR0; CCM_PDR0 = setting->pdr_val; if (!(pmcr0 & CCM_PMCR0_DFSUP_POST_DIVIDERS)) { /* Update the PLL settings */ if (pmcr0 & CCM_PMCR0_DFSUP_MCUPLL) CCM_MPCTL = setting->pll_val; else CCM_SPCTL = setting->pll_val; } cpu_frequency = ccm_get_mcu_clk(); if (dvfs_running) CCM_PMCR0 &= ~CCM_PMCR0_FSVAIM; if (pmcr0 & CCM_PMCR0_DPTEN) { update_dptc_counts(); enable_dptc(); } } /* Start DVFS, change the set point and stop it */ static void set_current_dvfs_level(unsigned int level) { /* Have to wait at least 3 div3 clocks before enabling after being * stopped before calling. */ updten_wait(); int oldlevel = disable_irq_save(); CCM_PMCR0 |= CCM_PMCR0_DVFEN; do_dvfs_update(CCM_PMCR0, level); restore_irq(oldlevel); udelay(DVFS_TVWAIT); oldlevel = disable_irq_save(); dvfs_int_voltage_wait_complete(); restore_irq(oldlevel); updten_wait(); bitclr32(&CCM_PMCR0, CCM_PMCR0_DVFEN); } /* Interrupt handler for DVFS */ static void dvfs_int(void) { unsigned long pmcr0 = CCM_PMCR0; unsigned long fsvai = pmcr0 & CCM_PMCR0_FSVAI; unsigned int level = (pmcr0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS; if (pmcr0 & CCM_PMCR0_FSVAIM) return; /* Do nothing. DVFS interrupt is masked. */ if (!(pmcr0 & CCM_PMCR0_UPDTEN)) return; /* Do nothing. DVFS didn't finish previous flow update. */ switch (fsvai) { case CCM_PMCR0_FSVAI_DECREASE: if (level >= DVFS_NUM_LEVELS - 1) return; /* DVFS already at lowest level */ /* Upon the DECREASE event, the frequency will be changed to the next * higher state index. */ while (((1u << ++level) & DVFS_LEVEL_MASK) == 0); dvfs_nr_dn++; break; /* Single-step frequency increase */ case CCM_PMCR0_FSVAI_INCREASE: if (level == 0) return; /* DVFS already at highest level */ /* Upon the INCREASE event, the frequency will be changed to the next * lower state index. */ while (((1u << --level) & DVFS_LEVEL_MASK) == 0); dvfs_nr_up++; break; /* Right to highest if panic */ case CCM_PMCR0_FSVAI_INCREASE_NOW: if (level == 0) return; /* DVFS already at highest level */ /* Upon the INCREASE_NOW event, the frequency will be increased to * the maximum (index 0). */ level = 0; dvfs_nr_pnc++; break; case CCM_PMCR0_FSVAI_NO_INT: dvfs_nr_no++; return; /* Do nothing. Freq change is not required */ } /* end switch */ /* Mask DVFS interrupt until voltage wait is complete */ pmcr0 |= CCM_PMCR0_FSVAIM; do_dvfs_update(pmcr0, level); /* Complete this in a few microseconds from now */ uevent(DVFS_TVWAIT, dvfs_int_voltage_wait_complete); } /* Interrupt vector for DVFS */ static __attribute__((interrupt("IRQ"))) void CCM_DVFS_HANDLER(void) { dvfs_int(); } /* Initialize the DVFS hardware */ static void INIT_ATTR dvfs_init(void) { /* Combine SW1A and SW1B DVS pins for a possible five DVS levels * per working point. Four, MAXIMUM, are actually used, one for each * frequency. */ mc13783_set(MC13783_ARBITRATION_SWITCHERS, MC13783_SW1ABDVS); /* Set DVS speed to 25mV every 4us. */ mc13783_write_masked(MC13783_SWITCHERS4, MC13783_SW1ADVSSPEED_4US, MC13783_SW1ADVSSPEED); /* Set DVFS pins to functional outputs. Input mode and pad setting is * fixed in hardware. */ iomuxc_set_pin_mux(IOMUXC_DVFS0, IOMUXC_MUX_OUT_FUNCTIONAL | IOMUXC_MUX_IN_NONE); iomuxc_set_pin_mux(IOMUXC_DVFS1, IOMUXC_MUX_OUT_FUNCTIONAL | IOMUXC_MUX_IN_NONE); #ifndef DVFS_NO_PWRRDY /* Configure PWRRDY signal pin. */ bitclr32(&GPIO1_GDIR, (1 << 5)); iomuxc_set_pin_mux(IOMUXC_GPIO1_5, IOMUXC_MUX_OUT_FUNCTIONAL | IOMUXC_MUX_IN_FUNCTIONAL); #endif /* GP load bits disabled */ bitclr32(&CCM_PMCR1, 0xf); /* Initialize DVFS signal weights and detection modes. */ int i; for (i = 0; i < 16; i++) { dvfs_set_lt_weight(i, lt_signals[i].weight); dvfs_set_lt_detect(i, lt_signals[i].detect); } /* Set up LTR0. */ bitmod32(&CCM_LTR0, DVFS_UPTHR << CCM_LTR0_UPTHR_POS | DVFS_DNTHR << CCM_LTR0_DNTHR_POS | DVFS_DIV3CK << CCM_LTR0_DIV3CK_POS, CCM_LTR0_UPTHR | CCM_LTR0_DNTHR | CCM_LTR0_DIV3CK); /* Set up LTR1. */ bitmod32(&CCM_LTR1, DVFS_DNCNT << CCM_LTR1_DNCNT_POS | DVFS_UPCNT << CCM_LTR1_UPCNT_POS | DVFS_PNCTHR << CCM_LTR1_PNCTHR_POS | CCM_LTR1_LTBRSR, CCM_LTR1_DNCNT | CCM_LTR1_UPCNT | CCM_LTR1_PNCTHR | CCM_LTR1_LTBRSR); /* Set up LTR2-- EMA configuration. */ bitmod32(&CCM_LTR2, DVFS_EMAC << CCM_LTR2_EMAC_POS, CCM_LTR2_EMAC); /* DVFS interrupt goes to MCU. Mask load buffer full interrupt. Do not * always give an event. */ bitmod32(&CCM_PMCR0, CCM_PMCR0_DVFIS | CCM_PMCR0_LBMI, CCM_PMCR0_DVFIS | CCM_PMCR0_LBMI | CCM_PMCR0_DVFEV); /* Initialize current core PLL and dividers for default level. Assumes * clocking scheme has been set up appropriately in other init code. */ ccm_set_mcupll_and_pdr(dvfs_clock_table[DVFS_LEVEL_DEFAULT].pll_val, dvfs_clock_table[DVFS_LEVEL_DEFAULT].pdr_val); /* Set initial level and working point. */ udelay(1500); set_current_dvfs_level(DVFS_LEVEL_DEFAULT); logf("DVFS: Initialized"); } /** DPTC **/ /* Request tracking since boot */ static bool dptc_running = false; /* Has driver enabled DPTC? */ unsigned int dptc_nr_dn = 0; unsigned int dptc_nr_up = 0; unsigned int dptc_nr_pnc = 0; unsigned int dptc_nr_no = 0; struct dptc_async_buf { struct spi_transfer_desc xfer; /* transfer descriptor */ unsigned int wp; /* new working point */ uint32_t buf[2]; /* buffer for async write */ }; static struct dptc_async_buf dptc_async_buf; /* ISR async write buffer */ static const unsigned char dptc_pmic_regs[2] = /* Register subaddresses */ { MC13783_SWITCHERS0, MC13783_SWITCHERS1 }; static uint32_t dptc_reg_shadows[2]; /* shadow regs */ /* Called (in SPI INT context) after asynchronous PMIC write is completed */ static void dptc_transfer_done_callback(struct spi_transfer_desc *xfer) { if (xfer->count != 0) return; /* Save new working point */ dptc_wp = ((struct dptc_async_buf *)xfer)->wp; update_dptc_counts(); if (dptc_running) enable_dptc(); } /* Handle the DPTC interrupt and sometimes the manual setting - always call * with IRQ masked. */ static void dptc_int(unsigned long pmcr0, int wp, struct dptc_async_buf *abuf) { const union dvfs_dptc_voltage_table_entry *entry; uint32_t sw1a, sw1advs, sw1bdvs, sw1bstby; uint32_t switchers0, switchers1; /* Mask DPTC interrupt and disable DPTC until the change request is * serviced. */ CCM_PMCR0 = (pmcr0 & ~CCM_PMCR0_DPTEN) | CCM_PMCR0_PTVAIM; switch (pmcr0 & CCM_PMCR0_PTVAI) { case CCM_PMCR0_PTVAI_DECREASE: /* Decrease voltage request - increment working point */ wp++; dptc_nr_dn++; break; case CCM_PMCR0_PTVAI_INCREASE: /* Increase voltage request - decrement working point */ wp--; dptc_nr_up++; break; case CCM_PMCR0_PTVAI_INCREASE_NOW: /* Panic request - move immediately to panic working point if * decrement results in greater working point than DPTC_WP_PANIC. */ if (--wp > DPTC_WP_PANIC) wp = DPTC_WP_PANIC; dptc_nr_pnc++; break; case CCM_PMCR0_PTVAI_NO_INT: /* Just maintain at global level */ if (abuf == &dptc_async_buf) dptc_nr_no++; break; } /* Keep result in range */ if (wp < 0) wp = 0; else if (wp >= DPTC_NUM_WP) wp = DPTC_NUM_WP - 1; /* Get new regulator register settings, sanity check them and write them * in the background. */ entry = &dvfs_dptc_voltage_table[wp]; sw1a = check_regulator_setting(entry->sw1a); sw1advs = check_regulator_setting(entry->sw1advs); sw1bdvs = check_regulator_setting(entry->sw1bdvs); sw1bstby = check_regulator_setting(entry->sw1bstby); switchers0 = dptc_reg_shadows[0] & ~(MC13783_SW1A | MC13783_SW1ADVS); abuf->buf[0] = switchers0 | sw1a << MC13783_SW1A_POS | /* SW1A */ sw1advs << MC13783_SW1ADVS_POS; /* SW1ADVS */ switchers1 = dptc_reg_shadows[1] & ~(MC13783_SW1BDVS | MC13783_SW1BSTBY); abuf->buf[1] = switchers1 | sw1bdvs << MC13783_SW1BDVS_POS | /* SW1BDVS */ sw1bstby << MC13783_SW1BSTBY_POS; /* SW1BSTBY */ abuf->wp = wp; /* Save new for xfer completion handler */ mc13783_write_async(&abuf->xfer, dptc_pmic_regs, abuf->buf, 2, dptc_transfer_done_callback); } /* Handle setting the working point explicitly - always call with IRQ * masked */ static void dptc_new_wp(unsigned int wp) { struct dptc_async_buf buf; /* "NO_INT" so the working point isn't incremented, just set. */ dptc_int(CCM_PMCR0 & ~CCM_PMCR0_PTVAI, wp, &buf); /* Wait for PMIC write */ while (!spi_transfer_complete(&buf.xfer)) { enable_irq(); nop; nop; nop; nop; nop; disable_irq(); } } /* Interrupt vector for DPTC */ static __attribute__((interrupt("IRQ"))) void CCM_CLK_HANDLER(void) { dptc_int(CCM_PMCR0, dptc_wp, &dptc_async_buf); } /* Initialize the DPTC hardware */ static void INIT_ATTR dptc_init(void) { int oldlevel; /* Shadow the regulator registers */ mc13783_read_regs(dptc_pmic_regs, dptc_reg_shadows, 2); /* Set default, safe working point. */ oldlevel = disable_irq_save(); dptc_new_wp(DPTC_WP_DEFAULT); restore_irq(oldlevel); /* Interrupt goes to MCU, specified reference circuits enabled when * DPTC is active, DPTC counting range = 256 system clocks */ bitmod32(&CCM_PMCR0, CCM_PMCR0_PTVIS | DPTC_DRCE_MASK, CCM_PMCR0_PTVIS | CCM_PMCR0_DCR | CCM_PMCR0_DRCE0 | CCM_PMCR0_DRCE1 | CCM_PMCR0_DRCE2 | CCM_PMCR0_DRCE3); logf("DPTC: Initialized"); } /** Main module interface **/ /** DVFS+DPTC **/ /* Initialize DVFS and DPTC */ void INIT_ATTR dvfs_dptc_init(void) { /* DVFS or DPTC on for some reason? Force off. */ bitmod32(&CCM_PMCR0, CCM_PMCR0_FSVAIM | CCM_PMCR0_LBMI | CCM_PMCR0_PTVAIM, CCM_PMCR0_FSVAIM | CCM_PMCR0_LBMI | CCM_PMCR0_DVFEN | CCM_PMCR0_PTVAIM | CCM_PMCR0_DPTEN); /* Ensure correct order - after this, the two appear independent */ dptc_init(); dvfs_init(); } /* Obtain the current core voltage setting, in millivolts 8-) */ unsigned int dvfs_dptc_get_voltage(void) { unsigned int v; int oldlevel = disable_irq_save(); v = dvfs_dptc_voltage_table[dptc_wp].sw[dvfs_level]; restore_irq(oldlevel); /* 25mV steps from 0.900V to 1.675V */ return v * 25 + 900; } /** DVFS **/ /* Start the DVFS hardware */ void dvfs_start(void) { if (dvfs_running) return; /* Have to wait at least 3 div3 clocks before enabling after being * stopped. */ udelay(1500); /* Unmask DVFS interrupt source and enable DVFS. */ bitmod32(&CCM_PMCR0, CCM_PMCR0_DVFEN, CCM_PMCR0_FSVAIM | CCM_PMCR0_DVFEN); dvfs_running = true; avic_enable_int(INT_CCM_DVFS, INT_TYPE_IRQ, INT_PRIO_DVFS, CCM_DVFS_HANDLER); logf("DVFS: started"); } /* Stop the DVFS hardware and return to default frequency */ void dvfs_stop(void) { if (!dvfs_running) return; uevent_cancel(); /* Mask DVFS interrupts. */ avic_disable_int(INT_CCM_DVFS); bitset32(&CCM_PMCR0, CCM_PMCR0_FSVAIM | CCM_PMCR0_LBMI); dvfs_running = false; /* Set default frequency level */ set_current_dvfs_level(DVFS_LEVEL_DEFAULT); logf("DVFS: stopped"); } /* Is DVFS enabled? */ bool dvfs_enabled(void) { return dvfs_running; } /* If DVFS is disabled, set the level explicitly */ void dvfs_set_level(unsigned int level) { if (dvfs_running || level >= DVFS_NUM_LEVELS || !((1 << level) & DVFS_LEVEL_MASK) || level == ((CCM_PMCR0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS)) return; udelay(1500); set_current_dvfs_level(level); } /* Get the current DVFS level */ unsigned int dvfs_get_level(void) { return dvfs_level; } /* Get bitmask of levels supported */ unsigned int dvfs_level_mask(void) { return DVFS_LEVEL_MASK; } /* Mask the DVFS interrupt without affecting running status */ void dvfs_int_mask(bool mask) { if (mask) { /* Just disable, not running = already disabled */ avic_mask_int(INT_CCM_DVFS); } else if (dvfs_running) { /* DVFS is running; unmask it */ avic_unmask_int(INT_CCM_DVFS); } } /* Set a signal load tracking weight */ void dvfs_set_lt_weight(enum DVFS_LT_SIGS index, unsigned long value) { volatile unsigned long *reg_p = &CCM_LTR2; unsigned int shift = 3 * index; if (index < 9) { reg_p = &CCM_LTR3; shift += 5; /* Bits 7:5, 10:8 ... 31:29 */ } else if (index < 16) { shift -= 16; /* Bits 13:11, 16:14 ... 31:29 */ } bitmod32(reg_p, value << shift, 0x7 << shift); } /* Return a signal load tracking weight */ unsigned long dvfs_get_lt_weight(enum DVFS_LT_SIGS index) { volatile unsigned long *reg_p = &CCM_LTR2; unsigned int shift = 3 * index; if (index < 9) { reg_p = &CCM_LTR3; shift += 5; /* Bits 7:5, 10:8 ... 31:29 */ } else if (index < 16) { shift -= 16; /* Bits 13:11, 16:14 ... 31:29 */ } return (*reg_p & (0x7 << shift)) >> shift; } /* Set a signal load detection mode */ void dvfs_set_lt_detect(enum DVFS_LT_SIGS index, bool edge) { unsigned long bit = 0; if ((unsigned)index < 13) bit = 1ul << (index + 3); else if ((unsigned)index < 16) bit = 1ul << (index + 29); bitmod32(&CCM_LTR0, edge ? bit : 0, bit); } /* Returns a signal load detection mode */ bool dvfs_get_lt_detect(enum DVFS_LT_SIGS index) { unsigned int shift = 32; if ((unsigned)index < 13) shift = index + 3; else if ((unsigned)index < 16) shift = index + 29; return !!((CCM_LTR0 & (1ul << shift)) >> shift); } /* Set/clear the general-purpose load tracking bit */ void dvfs_set_gp_bit(enum DVFS_DVGPS dvgp, bool assert) { if ((unsigned)dvgp <= 3) { unsigned long bit = 1ul << dvgp; bitmod32(&CCM_PMCR1, assert ? bit : 0, bit); } } /* Return the general-purpose load tracking bit */ bool dvfs_get_gp_bit(enum DVFS_DVGPS dvgp) { if ((unsigned)dvgp <= 3) return (CCM_PMCR1 & (1ul << dvgp)) != 0; return false; } /* Set GP load tracking by code. * level_code: * lt 0 =defaults * 0 =all off -> * 28 =highest load * gte 28=highest load * detect_mask bits: * b[3:0]: 1=LTn edge detect, 0=LTn level detect */ void dvfs_set_gp_sense(int level_code, unsigned long detect_mask) { int i; for (i = 0; i <= 3; i++) { int ltsig_num = DVFS_LT_SIG_DVGP0 + i; int gpw_num = DVFS_DVGP_0 + i; unsigned long weight; bool edge; bool assert; if (level_code < 0) { /* defaults */ detect_mask = 0; assert = 0; weight = lt_signals[ltsig_num].weight; edge = lt_signals[ltsig_num].detect != 0; } else { weight = MIN(level_code, 7); edge = !!(detect_mask & 1); assert = weight > 0; detect_mask >>= 1; level_code -= 7; if (level_code < 0) level_code = 0; } dvfs_set_lt_weight(ltsig_num, weight); /* set weight */ dvfs_set_lt_detect(ltsig_num, edge); /* set detect mode */ dvfs_set_gp_bit(gpw_num, assert); /* set activity */ } } /* Return GP weight settings */ void dvfs_get_gp_sense(int *level_code, unsigned long *detect_mask) { int i; int code = 0; unsigned long mask = 0; for (i = DVFS_LT_SIG_DVGP0; i <= DVFS_LT_SIG_DVGP3; i++) { code += dvfs_get_lt_weight(i); mask = (mask << 1) | (dvfs_get_lt_detect(i) ? 1 : 0); } if (level_code) *level_code = code; if (detect_mask) *detect_mask = mask; } /* Turn the wait-for-interrupt monitoring on or off */ void dvfs_wfi_monitor(bool on) { bitmod32(&CCM_PMCR0, on ? 0 : CCM_PMCR0_WFIM, CCM_PMCR0_WFIM); } /** DPTC **/ /* Start DPTC module */ void dptc_start(void) { int oldlevel; if (dptc_running) return; oldlevel = disable_irq_save(); dptc_running = true; /* Enable DPTC and unmask interrupt. */ update_dptc_counts(); enable_dptc(); restore_irq(oldlevel); avic_enable_int(INT_CCM_CLK, INT_TYPE_IRQ, INT_PRIO_DPTC, CCM_CLK_HANDLER); logf("DPTC: started"); } /* Stop the DPTC hardware if running and go back to default working point */ void dptc_stop(void) { int oldlevel; if (!dptc_running) return; avic_disable_int(INT_CCM_CLK); oldlevel = disable_irq_save(); dptc_running = false; /* Disable DPTC and mask interrupt. */ CCM_PMCR0 = (CCM_PMCR0 & ~CCM_PMCR0_DPTEN) | CCM_PMCR0_PTVAIM; /* Go back to default working point. */ dptc_new_wp(DPTC_WP_DEFAULT); restore_irq(oldlevel); logf("DPTC: stopped"); } /* Is DPTC enabled? */ bool dptc_enabled(void) { return dptc_running; } /* If DPTC is not running, set the working point explicitly */ void dptc_set_wp(unsigned int wp) { if (!dptc_running && wp < DPTC_NUM_WP) dptc_new_wp(wp); } /* Get the current DPTC working point */ unsigned int dptc_get_wp(void) { return dptc_wp; }