LED: Introduce LED blinking pattern code

It might be useful to display some different blinking patterns to
indicate specific system state (such as DFU mode vs. regular firmware)
diff --git a/firmware/apps/cardem/main.c b/firmware/apps/cardem/main.c
index 7402ab7..7d90eb0 100644
--- a/firmware/apps/cardem/main.c
+++ b/firmware/apps/cardem/main.c
@@ -121,9 +121,8 @@
 	enum confNum last_simtrace_config = simtrace_config;
 	unsigned int i = 0;
 
-	LED_Configure(LED_NUM_RED);
-	LED_Configure(LED_NUM_GREEN);
-	LED_Set(LED_NUM_RED);
+	led_init();
+	led_blink(LED_RED, BLINK_3O_5F);
 
 	/* Enable watchdog for 500ms, with no window */
 	WDT_Enable(WDT, WDT_MR_WDRSTEN | WDT_MR_WDDBGHLT | WDT_MR_WDIDLEHLT |
@@ -195,8 +194,6 @@
 			}
 		} else if (isUsbConnected == 0) {
 			TRACE_INFO("USB is now configured\n\r");
-			LED_Set(LED_NUM_GREEN);
-			LED_Clear(LED_NUM_RED);
 
 			isUsbConnected = 1;
 		}
diff --git a/firmware/apps/dfu/main.c b/firmware/apps/dfu/main.c
index 32c855c..6cdad20 100644
--- a/firmware/apps/dfu/main.c
+++ b/firmware/apps/dfu/main.c
@@ -177,10 +177,9 @@
 	uint8_t isUsbConnected = 0;
 	unsigned int i = 0;
 
-	LED_Configure(LED_NUM_RED);
-	LED_Configure(LED_NUM_GREEN);
-	LED_Set(LED_NUM_RED);
-
+	led_init();
+	led_blink(LED_GREEN, BLINK_3O_30F);
+	led_blink(LED_RED, BLINK_3O_30F);
 
 	/* Enable watchdog for 500ms, with no window */
 	WDT_Enable(WDT, WDT_MR_WDRSTEN | WDT_MR_WDDBGHLT | WDT_MR_WDIDLEHLT |
@@ -243,8 +242,6 @@
 			}
 		} else if (isUsbConnected == 0) {
 			TRACE_INFO("USB is now configured\n\r");
-			LED_Set(LED_NUM_GREEN);
-			LED_Clear(LED_NUM_RED);
 
 			isUsbConnected = 1;
 		}
diff --git a/firmware/libboard/common/include/board_common.h b/firmware/libboard/common/include/board_common.h
index 02b9e50..aeb0907 100644
--- a/firmware/libboard/common/include/board_common.h
+++ b/firmware/libboard/common/include/board_common.h
@@ -39,11 +39,11 @@
 
 #define BOARD_MCK     48000000
 
-#define LED_RED PIO_PA17
-#define LED_GREEN PIO_PA18
+#define PIO_LED_RED	PIO_PA17
+#define PIO_LED_GREEN	PIO_PA17
 
-#define PIN_LED_RED     {LED_RED, PIOA, ID_PIOA, PIO_OUTPUT_1, PIO_DEFAULT}
-#define PIN_LED_GREEN   {LED_GREEN, PIOA, ID_PIOA, PIO_OUTPUT_1, PIO_DEFAULT}
+#define PIN_LED_RED     {PIO_LED_RED, PIOA, ID_PIOA, PIO_OUTPUT_1, PIO_DEFAULT}
+#define PIN_LED_GREEN   {PIO_LED_GREEN, PIOA, ID_PIOA, PIO_OUTPUT_1, PIO_DEFAULT}
 #define PINS_LEDS       PIN_LED_RED, PIN_LED_GREEN 
 
 #define LED_NUM_RED     0
diff --git a/firmware/libboard/common/include/led.h b/firmware/libboard/common/include/led.h
index 87e2fc9..d1db039 100644
--- a/firmware/libboard/common/include/led.h
+++ b/firmware/libboard/common/include/led.h
@@ -1,72 +1,28 @@
-/* ----------------------------------------------------------------------------
- *         ATMEL Microcontroller Software Support 
- * ----------------------------------------------------------------------------
- * Copyright (c) 2008, Atmel Corporation
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the disclaimer below.
- *
- * Atmel's name may not be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
- * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
- * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
- * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- * ----------------------------------------------------------------------------
- */
+#pragma once
 
-/**
- * \file
- *
- *  \section Purpose
- * 
- *  Small set of functions for simple and portable LED usage.
- * 
- *  \section Usage
- * 
- *  -# Configure one or more LEDs using LED_Configure and
- *     LED_ConfigureAll.
- *  -# Set, clear and toggle LEDs using LED_Set, LED_Clear and
- *     LED_Toggle.
- * 
- *  LEDs are numbered starting from 0; the number of LEDs depend on the
- *  board being used. All the functions defined here will compile properly
- *  regardless of whether the LED is defined or not; they will simply
- *  return 0 when a LED which does not exist is given as an argument.
- *  Also, these functions take into account how each LED is connected on to
- *  board; thus, \ref LED_Set might change the level on the corresponding pin
- *  to 0 or 1, but it will always light the LED on; same thing for the other
- *  methods.
- */
+enum led {
+	LED_RED,
+	LED_GREEN,
+	_NUM_LED
+};
 
-#ifndef _LED_
-#define _LED_
+enum led_pattern {
+	BLINK_ALWAYS_OFF	= 0,
+	BLINK_ALWAYS_ON		= 1,
+	BLINK_3O_5F		= 2,
+	BLINK_3O_30F		= 3,
+	BLINK_3O_1F_3O_30F	= 4,
+	BLINK_3O_1F_3O_1F_3O_30F= 5,
+	BLINK_200O_F		= 6,
+	BLINK_600O_F		= 7,
+	BLINK_CUSTOM		= 8,
+	_NUM_LED_BLINK
+};
 
-#include <stdint.h>
+void led_init(void);
+void led_fini(void);
+void led_stop(void);
+void led_start(void);
 
-//------------------------------------------------------------------------------
-//         Global Functions
-//------------------------------------------------------------------------------
-
-extern uint32_t LED_Configure( uint32_t dwLed ) ;
-
-extern uint32_t LED_Set( uint32_t dwLed ) ;
-
-extern uint32_t LED_Clear( uint32_t dwLed ) ;
-
-extern uint32_t LED_Toggle( uint32_t dwLed ) ;
-
-#endif /* #ifndef LED_H */
-
+void led_blink(enum led led, enum led_pattern blink);
+enum led_pattern led_get(enum led led);
diff --git a/firmware/libboard/common/source/board_lowlevel.c b/firmware/libboard/common/source/board_lowlevel.c
index 5865d1d..98eb74d 100644
--- a/firmware/libboard/common/source/board_lowlevel.c
+++ b/firmware/libboard/common/source/board_lowlevel.c
@@ -127,9 +127,9 @@
 	    	      SUPC_SMMR_SMRSTEN_ENABLE;

 

     /* enable both LED and green LED */

-    PIOA->PIO_PER |= LED_RED | LED_GREEN;

-    PIOA->PIO_OER |= LED_RED | LED_GREEN;

-    PIOA->PIO_CODR |= LED_RED | LED_GREEN;

+    PIOA->PIO_PER |= PIO_LED_RED | PIO_LED_GREEN;

+    PIOA->PIO_OER |= PIO_LED_RED | PIO_LED_GREEN;

+    PIOA->PIO_CODR |= PIO_LED_RED | PIO_LED_GREEN;

 

     /* Set 3 FWS for Embedded Flash Access */

     EFC->EEFC_FMR = EEFC_FMR_FWS(3);

@@ -169,7 +169,7 @@
 #endif

 

     /* disable the red LED after main clock initialization */

-    PIOA->PIO_SODR = LED_RED;

+    PIOA->PIO_SODR = PIO_LED_RED;

 

     /* "switch" to main clock as master clock source (should already be the case */

     PMC->PMC_MCKR = (PMC->PMC_MCKR & ~(uint32_t)PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK;

diff --git a/firmware/libboard/common/source/led.c b/firmware/libboard/common/source/led.c
index 1a88e45..e4d2d57 100644
--- a/firmware/libboard/common/source/led.c
+++ b/firmware/libboard/common/source/led.c
@@ -1,168 +1,258 @@
-/* ----------------------------------------------------------------------------

- *         ATMEL Microcontroller Software Support

- * ----------------------------------------------------------------------------

- * Copyright (c) 2008, Atmel Corporation

- *

- * All rights reserved.

- *

- * Redistribution and use in source and binary forms, with or without

- * modification, are permitted provided that the following conditions are met:

- *

- * - Redistributions of source code must retain the above copyright notice,

- * this list of conditions and the disclaimer below.

- *

- * Atmel's name may not be used to endorse or promote products derived from

- * this software without specific prior written permission.

- *

- * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR

- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF

- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE

- * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,

- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT

- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,

- * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,

- * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

- * ----------------------------------------------------------------------------

- */

-

-/**

- * \file

- */

-

-/*------------------------------------------------------------------------------

- *         Headers

- *------------------------------------------------------------------------------*/

-

-#include "board.h"

-

-/*------------------------------------------------------------------------------

- *         Local Variables

- *------------------------------------------------------------------------------*/

-

-#ifdef PINS_LEDS

-static const Pin pinsLeds[] = { PINS_LEDS } ;

-static const uint32_t numLeds = PIO_LISTSIZE( pinsLeds ) ;

-#endif

-

-/*------------------------------------------------------------------------------

- *         Global Functions

- *------------------------------------------------------------------------------*/

-

-/**

- *  Configures the pin associated with the given LED number. If the LED does

- *  not exist on the board, the function does nothing.

- *  \param led  Number of the LED to configure.

- *  \return 1 if the LED exists and has been configured; otherwise 0.

- */

-extern uint32_t LED_Configure( uint32_t dwLed )

-{

-#ifdef PINS_LEDS

-    // Check that LED exists

-    if ( dwLed >= numLeds)

-    {

-

-        return 0;

-    }

-

-    // Configure LED

-    return ( PIO_Configure( &pinsLeds[dwLed], 1 ) ) ;

-#else

-    return 0 ;

-#endif

-}

-

-/**

- *  Turns the given LED on if it exists; otherwise does nothing.

- *  \param led  Number of the LED to turn on.

- *  \return 1 if the LED has been turned on; 0 otherwise.

- */

-extern uint32_t LED_Set( uint32_t dwLed )

-{

-#ifdef PINS_LEDS

-    /* Check if LED exists */

-    if ( dwLed >= numLeds )

-    {

-        return 0 ;

-    }

-

-    /* Turn LED on */

-    if ( pinsLeds[dwLed].type == PIO_OUTPUT_0 )

-    {

-

-        PIO_Set( &pinsLeds[dwLed] ) ;

-    }

-    else

-    {

-        PIO_Clear( &pinsLeds[dwLed] ) ;

-    }

-

-    return 1 ;

-#else

-    return 0 ;

-#endif

-}

-

-/**

- *  Turns a LED off.

- *

- *  \param led  Number of the LED to turn off.

- *  \return 1 if the LED has been turned off; 0 otherwise.

- */

-extern uint32_t LED_Clear( uint32_t dwLed )

-{

-#ifdef PINS_LEDS

-    /* Check if LED exists */

-    if ( dwLed >= numLeds )

-    {

-        return 0 ;

-    }

-

-    /* Turn LED off */

-    if ( pinsLeds[dwLed].type == PIO_OUTPUT_0 )

-    {

-        PIO_Clear( &pinsLeds[dwLed] ) ;

-    }

-    else

-    {

-        PIO_Set( &pinsLeds[dwLed] ) ;

-    }

-

-    return 1 ;

-#else

-    return 0 ;

-#endif

-}

-

-/**

- *  Toggles the current state of a LED.

- *

- *  \param led  Number of the LED to toggle.

- *  \return 1 if the LED has been toggled; otherwise 0.

- */

-extern uint32_t LED_Toggle( uint32_t dwLed )

-{

-#ifdef PINS_LEDS

-    /* Check if LED exists */

-    if ( dwLed >= numLeds )

-    {

-        return 0 ;

-    }

-

-    /* Toggle LED */

-    if ( PIO_GetOutputDataStatus( &pinsLeds[dwLed] ) )

-    {

-        PIO_Clear( &pinsLeds[dwLed] ) ;

-    }

-    else

-    {

-        PIO_Set( &pinsLeds[dwLed] ) ;

-    }

-

-    return 1 ;

-#else

-    return 0 ;

-#endif

-}

-

+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocom/core/timer.h>
+
+#include "board.h"
+#include "utils.h"
+#include "led.h"
+
+#ifdef PINS_LEDS
+static const Pin pinsLeds[] = { PINS_LEDS } ;
+
+static void led_set(enum led led, int on)
+{
+	ASSERT(led < PIO_LISTSIZE(pinsLeds));
+
+	if (on)
+        	PIO_Set(&pinsLeds[led]);
+	else
+        	PIO_Clear(&pinsLeds[led]);
+}
+
+/* LED blinking code */
+
+/* a single state in a sequence of blinking */
+struct blink_state {
+	/* duration of the state in ms */
+	uint16_t duration;
+	/* bringhtness of LED during the state */
+	uint8_t on;
+} __attribute__((packed));
+
+static const struct blink_state bs_off[] = {
+	{ 0, 0 }
+};
+
+static const struct blink_state bs_on[] = {
+	{ 0, 1 }
+};
+
+static const struct blink_state bs_3on_5off[] = {
+	{ 300, 1 }, { 500, 0 }
+};
+
+static const struct blink_state bs_3on_30off[] = {
+	{ 300, 1 }, { 3000, 0 }
+};
+
+static const struct blink_state bs_3on_1off_3on_30off[] = {
+	{ 300, 1 }, { 100, 0 }, { 300, 1 }, { 3000, 0 }
+};
+
+static const struct blink_state bs_3on_1off_3on_1off_3on_30off[] = {
+	{ 300, 1 }, { 100, 0 }, { 300, 1 }, { 100, 0 }, { 300, 1 }, { 3000, 0 }
+};
+static const struct blink_state bs_200on_off[] = {
+	{ 20000, 1 }, { 0, 0 },
+};
+static const struct blink_state bs_600on_off[] = {
+	{ 60000, 1 }, { 0, 0 },
+};
+
+
+/* a blink pattern is an array of blink_states */
+struct blink_pattern {
+	const struct blink_state *states;
+	uint16_t size;
+};
+
+/* compiled-in default blinking patterns */
+static const struct blink_pattern patterns[] = {
+	[BLINK_ALWAYS_OFF] = {
+		.states = bs_off,
+		.size = ARRAY_SIZE(bs_off),
+	},
+	[BLINK_ALWAYS_ON] = {
+		.states = bs_on,
+		.size = ARRAY_SIZE(bs_on),
+	},
+	[BLINK_3O_5F] = {
+		.states = bs_3on_5off,
+		.size = ARRAY_SIZE(bs_3on_5off),
+	},
+	[BLINK_3O_30F] = {
+		.states = bs_3on_30off,
+		.size = ARRAY_SIZE(bs_3on_30off),
+	},
+	[BLINK_3O_1F_3O_30F] = {
+		.states = bs_3on_1off_3on_30off,
+		.size = ARRAY_SIZE(bs_3on_1off_3on_30off),
+	},
+	[BLINK_3O_1F_3O_1F_3O_30F] = {
+		.states = bs_3on_1off_3on_1off_3on_30off,
+		.size = ARRAY_SIZE(bs_3on_1off_3on_1off_3on_30off),
+	},
+	[BLINK_200O_F] = {
+		.states = bs_200on_off,
+		.size = ARRAY_SIZE(bs_200on_off),
+	},
+	[BLINK_600O_F] = {
+		.states = bs_600on_off,
+		.size = ARRAY_SIZE(bs_600on_off),
+	},
+};
+
+struct led_state {
+	/* which led are we handling */
+	enum led led;
+
+	/* timer */
+	struct osmo_timer_list timer;
+
+	/* pointer and size of blink array */
+	const struct blink_pattern *pattern;
+
+	unsigned int cur_state;
+	unsigned int illuminated;
+
+	/* static allocated space for custom blinking pattern */
+	struct blink_pattern pattern_cust;
+	struct blink_state blink_cust[10];
+};
+
+static unsigned int cur_state_inc(struct led_state *ls)
+{
+	ls->cur_state = (ls->cur_state + 1) % ls->pattern->size;
+	return ls->cur_state;
+}
+
+static const struct blink_state *
+next_blink_state(struct led_state *ls)
+{
+	return &ls->pattern->states[cur_state_inc(ls)];
+}
+
+/* apply the next state to the LED */
+static void apply_blinkstate(struct led_state *ls,
+			     const struct blink_state *bs)
+{
+	led_set(ls->led, bs->on);
+	ls->illuminated = bs->on;
+
+	/* re-schedule the timer */
+	if (bs->duration) {
+		uint32_t us = bs->duration * 1000;
+		osmo_timer_schedule(&ls->timer, us / 1000000, us % 1000000);
+	}
+}
+
+static void blink_tmr_cb(void *data)
+{
+	struct led_state *ls = data;
+	const struct blink_state *next_bs = next_blink_state(ls);
+
+	/* apply the next state to the LED */
+	apply_blinkstate(ls, next_bs);
+}
+
+static struct led_state led_state[] = {
+	[LED_GREEN] = {
+		.led = LED_GREEN,
+		.timer.cb = blink_tmr_cb,
+		.timer.data = &led_state[LED_GREEN],
+	},
+	[LED_RED] = {
+		.led = LED_RED,
+		.timer.cb = blink_tmr_cb,
+		.timer.data = &led_state[LED_RED],
+	},
+};
+#endif /* PINS_LEDS */
+
+void led_blink(enum led led, enum led_pattern blink)
+{
+#ifdef PINS_LEDS
+	struct led_state *ls;
+
+	if (led >= ARRAY_SIZE(led_state))
+		return;
+	ls = &led_state[led];
+
+	/* stop previous blinking, if any */
+	osmo_timer_del(&ls->timer);
+	led_set(led, 0);
+	ls->illuminated = 0;
+	ls->pattern = NULL;
+	ls->cur_state = 0;
+
+	switch (blink) {
+	case BLINK_CUSTOM:
+		ls->pattern = &ls->pattern_cust;
+		break;
+	default:
+		if (blink >= ARRAY_SIZE(patterns))
+			return;
+		ls->pattern = &patterns[blink];
+		break;
+	}
+
+	if (ls->pattern && ls->pattern->size > 0)
+		apply_blinkstate(ls, &ls->pattern->states[0]);
+#endif
+}
+
+enum led_pattern led_get(enum led led)
+{
+#ifdef PINS_LEDS
+	struct led_state *ls;
+	unsigned int i;
+
+	if (led >= ARRAY_SIZE(led_state))
+		return -1;
+	ls = &led_state[led];
+
+	if (ls->pattern == &ls->pattern_cust)
+		return BLINK_CUSTOM;
+
+	for (i = 0; i < ARRAY_SIZE(patterns); i++) {
+		if (ls->pattern == &patterns[i])
+			return i;
+	}
+#endif
+	/* default case, shouldn't be reached */
+	return -1;
+}
+
+void led_start(void)
+{
+	led_set(LED_GREEN, led_state[LED_GREEN].illuminated);
+	led_set(LED_RED, led_state[LED_RED].illuminated);
+}
+
+void led_stop(void)
+{
+	led_set(LED_GREEN, 0);
+	led_set(LED_RED, 0);
+}
+
+void led_init(void)
+{
+#ifdef PINS_LEDS
+	PIO_Configure(pinsLeds, PIO_LISTSIZE(pinsLeds));
+	led_set(LED_GREEN, 0);
+	led_set(LED_RED, 0);
+#endif
+}
+
+void led_fini(void)
+{
+#ifdef PINS_LEDS
+	/* we don't actually need to do this, but just in case... */
+	osmo_timer_del(&led_state[LED_RED].timer);
+	osmo_timer_del(&led_state[LED_GREEN].timer);
+	led_set(LED_GREEN, 0);
+	led_set(LED_RED, 0);
+#endif
+}