/*
* 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 */
}