/* * XIOH power sequence * Copyright (C) 2012, 2013 Avencall * * main.c * Authors: * Jean-Marc Ouvrard * Noe Rubinstein * Guillaume Knispel * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT: * * - FOR OFFICIAL BUILDS: *** mspgcc 20120406 *** * * - We also target (non-official builds only, less tested): * IAR C/C++ Compiler V5.40.2.30380/W32 * TI MSP430 Optimizing C/C++ Compiler v 4.1.1 * (primarily in COFF mode, should also build in ELF mode) * * - Unpatched mspgcc 20120406 from Debian Wheezy (4.6.3~mspgcc-20120406-3) * does not contain a fix for * "Assignment to chained volatiles can produce invalid assignment" * http://sourceforge.net/tracker/?func=detail&aid=3559978&group_id=42303&atid=432701 * => DO NOT CHAIN ASSIGN VOLATILES (like in: vol2 = vol1 = &something;) * * - Maintain UNEXPECTED_ISR_LIST (used when building with IAR and TI) * * - Do not build with TI Compiler with an optimization level superior to O1: * it results in both very poor code for switches, and some safety related * checks optimized out. * * - More generally, you should use the provided IAR / TI CCS projects and * mspgcc Makefile, and you should refrain from modifying the build options. * * - Regarding TI Compiler: * * in COFF mode, global and static declaration are handled differently * depending on whether they are explicitly initialized to zero in the * source code or not (in violation of the C standard). Use explicit = 0 * initializations when needed. * * in ELF mode, the compiler find it funny to define plain ints as * 32-bits ones. Given that the MSP430 is a 16-bits computer, you should * not use plain ints unless you are forced to or don't care much about * the produced code. * * - Some compiler supports C99, others C90 with extensions. Only use simple * constructs common to both C versions and never depend on subtleties. * * - The mspgcc build uses the -funsigned-char option so plain chars behave * like they do by default with IAR / TI compilers. */ /* Random notes: * * - We have decided to remove the reset button and to keep the power button * (v5 has both, v6 will only have the power button) * * We should make sure that the power button works more reliably especially * considering the ACPI specification and interactions with the EP80579 * standard ACPI subsystem. We should also debounce the On -> Off transition * of the power button. */ #include "compiler.h" #include "config.h" MASK_MISRA #include #include #include #include UNMASK_MISRA #include "def.h" #include "main.h" #include "hardware.h" #include "serial.h" //////////////////////////////////////////////////////////////////// // UNEXPECTED_ISR_LIST *must* be manually maintained for IAR and TI // (UNEXPECTED_ISR_LIST will not be used during an mspgcc build) // Note that this does not include the vectors that are undefined on this chip. #define UNEXPECTED_ISR_LIST \ PORT1_VECTOR,PORT2_VECTOR,ADC10_VECTOR,USCIAB0TX_VECTOR, \ USCIAB0RX_VECTOR,TIMERA0_VECTOR,WDT_VECTOR,TIMERB1_VECTOR, \ TIMERB0_VECTOR,NMI_VECTOR //////////////////////////////////////////////////////////////////// static void InitPorts(void); static void GlobalInit(void); volatile u16 SoftWD = 0x7fff ^ SOFTWD_KEY; volatile u16 TAVector; volatile u16 Timer1 = 0; volatile u16 Timer2 = 0; volatile u16 SW1State = 0; #ifdef RST_SW_N volatile u16 SW2State = 0; #endif /* RST_SW_N */ volatile u16 Timer_A_count = 0; #ifdef CAN_WAIT_TENSION //has to be coded on real board volatile u8 bV1P0 = true; volatile u8 bV1P2 = true; volatile u8 bV1P8_DDR = true; volatile u8 bV2P5 = true; volatile u8 bVCC3 = true; #define TENSION_EXPIRED (Timer1 == 0) #define TENSION_WAIT(t) (t) #else #define TENSION_EXPIRED (0) #define TENSION_WAIT(t) (Timer1 == 0) #endif /* CAN_WAIT_TENSION */ //////////////////////////////////////////////////////////////////// enum machine_state { MACHINE_OFF = SV(0), WAIT_START_CMDPWR = SV(1), WAIT_ATX_START_V1P2 = SV(2), WAIT_V1P2_START_V1P8 = SV(3), DEASSERT_RSMRST_N = SV(4), WAIT_V1P8_START_V1P0 = SV(5), WAIT_V1P0_ASSERT_VRMPWRGD = SV(6), ASSERT_CK410_PWR_GD_N = SV(7), ASSERT_SYS_PWR_OK = SV(8), PRESS_PWRBTN = SV(9), RELEASE_PWRBTN = SV(10), MACHINE_RUNNING = SV(11), STOP_INHIBIT = SV(12), STOP_FINAL = SV(13), }; #define STATE_UPPER_LIMIT STOP_FINAL enum reset_state { RST_NO = SV(0), RST_PRESSED = SV(1), RST_WAIT = SV(2), }; #define RESET_UPPER_LIMIT RST_WAIT //////////////////////////////////////////////////////////////////// u8 s3n_st = 0; #define S3N_COUNT_MASK 0x7fu #define S3N_DEB_MASK 0x80u #define S3N_ASSERTED (!(s3n_st & S3N_DEB_MASK)) #define S3N_DEASSERTED (s3n_st & S3N_DEB_MASK) /* NOTE: S3N_DEASSERT_MS must be > 1 for proper debouncing in all conditions */ #define S3N_DEASSERT_MS MS(2) #define S3N_ASSERT_COUNT 2 static inline void init_s3n_st(void) { s3n_st = Timer_A_count & S3N_COUNT_MASK; } static void update_s3n_st(void) { if (S3N_ASSERTED) { if (!SLP_S3_N(in)) init_s3n_st(); else if (((Timer_A_count - s3n_st) & S3N_COUNT_MASK) >= S3N_DEASSERT_MS) s3n_st = S3N_DEB_MASK; } else /* S3N_DEASSERTED */ { if (SLP_S3_N(in)) { s3n_st = S3N_DEB_MASK; } else { s3n_st++; if (s3n_st >= (S3N_DEB_MASK | S3N_ASSERT_COUNT)) init_s3n_st(); } } } //////////////////////////////////////////////////////////////////// // It seems that TI compiler does not honor inlines from ISR #define reboot() do { WDTCTL = 0; } while (1) /* Note that different bit extents are used in PF_KEY and _PF_MAX */ #define PF_KEY 0xE0 // autoboot after power failure max _PF_MAX+1 times (if power seq unsuccessful) #define _PF_MAX 0x03 // powerfail_recover MUST BE EXPLICITLY INITIALIZED TO 0. // (The "= 0" must be present in the source code.) // Initializing to 0 ensures that the board won't be unconditionally started // on an MSP power up -- this shall never be done unconditionally and at the // moment we have no code to check the conditions for which this would be safe. u8 powerfail_recover = 0; // THIS IS SAFETY-RELATED. static inline u8 PF_COUNT(u8 v) { return v ^ PF_KEY; } static inline bool PF_VALID(u8 v) { return (v == 0) || (PF_COUNT(v) <= _PF_MAX); } #define PF_MAX PF_COUNT(_PF_MAX) #define change_state(ns) \ do { \ trace_state(ns); \ state = (ns); \ } while (0) MASK_MISRA int main(void) UNMASK_MISRA { u16 state; #ifdef RST_SW_N u16 resetState; #endif /* RST_SW_N */ #ifndef WATCHDOG WDTCTL = WDTPW | WDTHOLD; #endif /* WATCHDOG */ __disable_interrupt(); GlobalInit(); InitPorts(); #ifdef TRACE_SERIAL SerialInit(); #endif /* TRACE_SERIAL */ __enable_interrupt(); state = MACHINE_OFF; #ifdef RST_SW_N resetState = RST_NO; #endif /* RST_SW_N */ while (1) { //////////////////////////////////////////////////////////////////// #ifdef RST_SW_N # ifdef TRACE_SERIAL // debug behavior switch (in_range(resetState, RESET_UPPER_LIMIT)) { case RST_NO: if (SW2State > MS(100)) { dump_trace(); resetState = RST_WAIT; } break; case RST_WAIT: if (SW2State == 0) resetState = RST_NO; break; } # else // normal behavior if (out_range(resetState, RESET_UPPER_LIMIT)) { Timer2 = 0; resetState = RST_WAIT; } switch (in_range(resetState, RESET_UPPER_LIMIT)) { case RST_NO: if (SW2State > MS(300)) { resetState = RST_PRESSED; } break; case RST_PRESSED: Set(SYS_RESET_N(dir)); Clr(SYS_RESET_N(ren)); Clr(SYS_RESET_N(out)); Timer2 = MS(150); resetState = RST_WAIT; break; case RST_WAIT: if (Timer2 == 0) { Set(SYS_RESET_N(out)); Set(SYS_RESET_N(ren)); Clr(SYS_RESET_N(dir)); if (SW2State == 0) resetState = RST_NO; } break; } # endif /* TRACE_SERIAL */ #endif /* RST_SW_N */ //////////////////////////////////////////////////////////////////// #if defined(TRACE_SERIAL) && defined(TEST_LOOP_SPEED) { u16 new_timer = Timer_A_count; u16 delta = new_timer - ref_Timer_A_count; if (delta) { if (tl_state > -2) tl_state--; if (tl_state == -1) tl_num = 0; if (tl_state == -2) tl_delta = delta; ref_Timer_A_count = new_timer; } if (tl_state == -1) tl_num++; } #endif trace_changes(state); if (out_range(state, STATE_UPPER_LIMIT)) reboot(); #define Xcase(c) case c: ResetSoftWD(); switch (in_range(state, STATE_UPPER_LIMIT)) { Xcase(MACHINE_OFF) Clr(CMDPWR(out)); #ifdef LOOP_REBOOT change_state(WAIT_START_CMDPWR); #endif if (powerfail_recover) { if (!PF_VALID(powerfail_recover)) reboot(); if (PF_COUNT(powerfail_recover)) powerfail_recover--; else powerfail_recover = 0; change_state(WAIT_START_CMDPWR); } else if (SW1State > MS(30)) change_state(WAIT_START_CMDPWR); break; Xcase(WAIT_START_CMDPWR) if (SW1State == 0) { Set(CMDPWR(out)); // Start Atx Power Supply Timer1 = MS(2000); change_state(WAIT_ATX_START_V1P2); } break; Xcase(WAIT_ATX_START_V1P2) /* WARNING: no timeout when not monitoring tensions */ if (SW1State || TENSION_EXPIRED) change_state(STOP_INHIBIT); if (ATX_PWROK(in) && TENSION_WAIT(bV2P5 && bVCC3)) { Clr(V1P2_CORE_EN_N(out)); Timer1 = MS(30); change_state(WAIT_V1P2_START_V1P8); } break; Xcase(WAIT_V1P2_START_V1P8) if (SW1State || TENSION_EXPIRED) change_state(STOP_INHIBIT); if (TENSION_WAIT(bV1P2)) { Timer1 = MS(30); Set(V1P8_CMD(out)); change_state(DEASSERT_RSMRST_N); } break; Xcase(DEASSERT_RSMRST_N) if (SW1State) change_state(STOP_INHIBIT); if (Timer1 < MS(19)) { Set(IMCH_RSMRST_N(out)); change_state(WAIT_V1P8_START_V1P0); } break; Xcase(WAIT_V1P8_START_V1P0) if (SW1State || TENSION_EXPIRED) change_state(STOP_INHIBIT); if (TENSION_WAIT(bV1P8)) { Clr(CPU_VCCP_EN_N(out)); Timer1 = MS(30); change_state(WAIT_V1P0_ASSERT_VRMPWRGD); } break; Xcase(WAIT_V1P0_ASSERT_VRMPWRGD) if (SW1State || TENSION_EXPIRED) change_state(STOP_INHIBIT); if (TENSION_WAIT(bV1P0)) { Set(VRMPWRGD(out)); Clr(GREEN_LED_N(out)); Timer1 = MS(3); change_state(ASSERT_CK410_PWR_GD_N); } break; Xcase(ASSERT_CK410_PWR_GD_N) if (!ATX_PWROK(in)) change_state(STOP_INHIBIT); if (Timer1 == 0) { Set(CK410_PWR_GD_N(dir)); // BUGBUG REN Clr(CK410_PWR_GD_N(out)); Timer1 = MS(105); change_state(ASSERT_SYS_PWR_OK); } break; Xcase(ASSERT_SYS_PWR_OK) if (!ATX_PWROK(in)) change_state(STOP_INHIBIT); if (Timer1 == 0) { Set(SYS_PWR_OK(out)); Timer1 = MS(10); change_state(PRESS_PWRBTN); } break; Xcase(PRESS_PWRBTN) if (!ATX_PWROK(in)) change_state(STOP_INHIBIT); if (Timer1 == 0) { // Start the EP80579 by driving IMCH_PWRBTN_N. // Doing it here seems to be reliable. // Testing has shown variations on S3N: // in some cases it is only released after // IMCH_PWRBTN_N has been driven, proving // that this is necessary. Set(IMCH_PWRBTN_N(dir)); Timer1 = MS(50); init_s3n_st(); change_state(RELEASE_PWRBTN); } break; Xcase(RELEASE_PWRBTN) if (!ATX_PWROK(in)) change_state(STOP_INHIBIT); update_s3n_st(); if ((Timer1 == 0) || S3N_DEASSERTED) { Clr(IMCH_PWRBTN_N(dir)); #ifdef LOOP_REBOOT Timer1 = LOOP_REBOOT; #endif change_state(MACHINE_RUNNING); } break; Xcase(MACHINE_RUNNING) #ifdef LOOP_REBOOT if (Timer1 == 0) change_state(STOP_INHIBIT); #endif powerfail_recover = PF_MAX; update_s3n_st(); if ((SW1State >= MS(3800)) // approx 4 seconds || S3N_ASSERTED) { powerfail_recover = 0; change_state(STOP_INHIBIT); } else if (!ATX_PWROK(in)) { change_state(STOP_INHIBIT); } break; Xcase(STOP_INHIBIT) InitPorts(); // Disable any other Power up for 3 s: Timer1 = MS(3000); Clr(RED_LED_N(out)); change_state(STOP_FINAL); break; Xcase(STOP_FINAL) if (SW1State > 0) powerfail_recover = 0; if ((Timer1 == 0) && (SW1State == 0)) { // Restart is possible now: all leds off Set(RED_LED_N(out)); change_state(MACHINE_OFF); } break; } } } #ifndef MY_GCC #pragma vector = UNEXPECTED_ISR_LIST #endif __interrupt void _unexpected_(void) { reboot(); } #ifdef MY_GCC void _endless_loop__(void) { reboot(); } #endif /* MY_GCC */ #pragma vector = TIMERA1_VECTOR __interrupt void Timer_A(void) { #ifdef WATCHDOG if ((SoftWD ^ SOFTWD_KEY) > MAX_SOFTWD) reboot(); else SoftWD--; /* ACLK (VLO) /64 => T belongs to [0.0032; 0.016] s */ WDTCTL = WDTPW | WDTCNTCL | WDTSSEL | WDTIS1 | WDTIS0; #endif /* WATCHDOG */ Timer_A_count++; if (!START_SW_N(in)) SW1State++; else SW1State = 0; #ifdef RST_SW_N if (!RST_SW_N(in)) SW2State++; else SW2State = 0; #endif /* RST_SW_N */ if (Timer1) Timer1--; if (Timer2) Timer2--; TAVector = TAIV; } static void InitPorts(void) { #define DECLARE_PORT_INIT(port) \ u8 port##SEL_init = 0; \ u8 port##OUT_init = 0; \ u8 port##REN_init = 0; \ u8 port##DIR_init = 0; DECLARE_PORT_INIT(P1) DECLARE_PORT_INIT(P2) DECLARE_PORT_INIT(P3) DECLARE_PORT_INIT(P4) #define GD__gen_init(port, bit, dir, ren, sel, out) \ { \ port##REN_init |= ((ren) ? (bit) : 0); \ port##SEL_init |= ((sel) ? (bit) : 0); \ port##DIR_init |= ((dir) ? (bit) : 0); \ port##OUT_init |= ((out) ? (bit) : 0); \ } ALL_SIGNALS(gen_init) #define INIT_PORT(port) \ port##SEL = port##SEL_init; \ port##OUT = port##OUT_init; \ port##REN = port##REN_init; \ port##DIR = port##DIR_init; P1IE = 0; P1IES = 0; P2IE = 0; P2IES = 0; INIT_PORT(P1) INIT_PORT(P2) INIT_PORT(P3) INIT_PORT(P4) } static void GlobalInit(void) { /******** MCLK SMCLK ACLK Configuration ********/ /* Freq selection procedure compliant with errata BCL12 at startup * (potentially switching from RSEL < 12 to > 13) * Note that we do not even need this workaround with an 8MHz freq * for the DCO (RSEL == 13). However, we might return to a 12MHz freq * on the v6.6 board, so this sequence shall not be changed. */ BCSCTL2 = DIVS_2 | DIVM_2; /* MCLK and SMCLK Divider: /4 */ DCOCTL = 0; /* LFXT1 low freq (to allow for VLO), ACLK will be /1 */ BCSCTL1 = CALBC1_8MHZ & ~(XTS | DIVA0 | DIVA1); DCOCTL = CALDCO_8MHZ; BCSCTL3 = LFXT1S_2 /* VLO */; /* Status here: * DCO: 8MHz nominal. * MCLK and SMCLK: DCO/4 = 2MHz nominal. * ACLK from VLOCLK: 4 -> 20kHz. */ #ifdef WATCHDOG /******** Watchdog early configuration ********/ /* ACLK /8192 */ WDTCTL = WDTPW + WDTCNTCL + WDTSSEL + WDTIS0; /* This initial WDT will expire in 0.4 to 2.1 s * Note that it will be reconfigured to expire with shorter intervals * starting from the next clearing. */ #endif /* WATCHDOG */ /******** Soft Watchdog ********/ SoftWD = MAX_SOFTWD ^ SOFTWD_KEY; /******** Timer A configuration ********/ // SMCLK | div by 1 | reset | enable interrupt | UP TACTL = TASSEL_2 | ID_0 | TACLR | TAIE | MC_1; /* ======================================= * * Calibrated DCO Frequencies - Tolerance Over Temperature 0C to 85C * Min Typ Max * BCSCTL1 = CALBC1_8MHZ, 2.2 V 7.76 8 8.4 * fCAL(8MHz) DCOCTL = CALDCO_8MHZ, 3 V 7.8 8 8.2 MHz * Gating time: 5 ms 3.6 V 7.6 8 8.24 * * BCSCTL1 = CALBC1_12MHZ, 2.2 V 11.7 12 12.3 * fCAL(12MHz) DCOCTL = CALDCO_12MHZ, 3 V 11.7 12 12.3 MHz * Gating time: 5 ms 3.6 V 11.7 12 12.3 * * Currently used values 7.6 -> 8.4 MHz = 8 MHz +/- 5% * Set Timer_A to 1 ms period min : * * ========================================== from math import ceil def taccr0(min_freq, nom_freq, max_freq, divisor, min_target_period): min_div_freq = float(min_freq) / divisor nom_div_freq = float(nom_freq) / divisor max_div_freq = float(max_freq) / divisor count = ceil(max_div_freq * min_target_period) minp = (count / max_div_freq) * 1000. nomp = (count / nom_div_freq) * 1000. maxp = ((count + 1) / min_div_freq) * 1000. print "\t/""* (TA16 errata => + 1) *""/" print "\tTACCR0 =", int(count), "+ 1;\t" + \ ("/""* T: min=%.3f nom=%.3f max=%.3f ms *""/") % (minp, nomp, maxp) taccr0(7600000, 8000000, 8400000, 4, 0.001) * ========================================== */ /* !!!WARNING!!! CHECK FOR 1 MS PERIOD */ #if MS(100) != 100 # error change definition of MS in config.h #endif /* (TA16 errata => + 1) */ TACCR0 = 2100 + 1; /* T: min=1.000 nom=1.050 max=1.106 ms */ }