aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/eeprom.c
blob: a789af7babd727abaac32050ce60faa4aac598c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/*
 * Simple PWM controller
 * EEPROM
 *
 * Copyright (c) 2020 Michael Buesch <m@bues.ch>
 *
 * 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 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "compat.h"
#include "eeprom.h"

#include "arithmetic.h"
#include "debug.h"
#include "ring.h"
#include "standby.h"
#include "util.h"
#include "watchdog.h"


#define EEPROM_STORE_DELAY_MS	1500u


#ifndef ee_addr_t
# ifdef EEARH
  typedef uint16_t ee_addr_t;
# else
  typedef uint8_t ee_addr_t;
# endif
# define ee_addr_t ee_addr_t
#endif

#define EE_RING_SIZE		((E2END + 1u) / sizeof(struct eeprom_data))
#define EE_RING_MAX_INDEX	(EE_RING_SIZE - 1u)


static struct eeprom_data EEMEM eep_addrspace[EE_RING_SIZE] = {
	{
		.flags		= 0u,
		.setpoints	= { },
		.pwmcorr_mul	= { },
		.pwmcorr_div	= { },
		.serial		= 0u,
	},
};


static struct {
	struct eeprom_data shadow_copy;	/* Copy of EEPROM. */
	struct eeprom_data work_copy;	/* Copy that can be modified. */
	uint8_t ee_index;		/* Ring index. */
	uint8_t ee_write_offset;	/* Byte offset of current write. */
	uint16_t store_timer_ms;	/* Store delay timer. */
	bool store_request;		/* EEPROM write has been requested. */
	bool store_running;		/* EEPROM write is currently running. */
} eep;


static void eeprom_update_standby_suppress(void)
{
	if (USE_EEPROM && USE_DEEP_SLEEP) {
		set_standby_suppress(STANDBY_SRC_EEPROM,
				     eep.store_running || eep.store_request);
	}

}

/* Convert a pointer to the EEPROM address space into an integer. */
static ee_addr_t ptr_to_eeaddr(const void *eemem_ptr)
{
	return (ee_addr_t)(uintptr_t)eemem_ptr;
}

/* Read one byte from EEPROM. */
static uint8_t ee_read_byte(ee_addr_t addr)
{
	uint8_t data = 0u;

	if (USE_EEPROM) {
		eeprom_busy_wait();
		EEAR = addr;
		EECR |= (1u << EERE);
		data = EEDR;
	}

	return data;
}

/* Read a block of bytes from EEPROM. */
static void ee_read_block(void *dest, ee_addr_t addr, uint8_t count)
{
	uint8_t *d = (uint8_t *)dest;

	if (USE_EEPROM) {
		for ( ; count; count--, d++, addr++)
			*d = ee_read_byte(addr);
	}
}

/* EEPROM interrupt service routine. */
#if USE_EEPROM
ISR(EE_READY_vect)
{
	ee_addr_t address;
	uint8_t data;
	uint8_t offset;
	uint8_t index = 0u;

	index = eep.ee_index;
	offset = eep.ee_write_offset;

	address = ptr_to_eeaddr(&eep_addrspace[index]) + offset;
	data = *((uint8_t *)&eep.work_copy + offset);

	EEAR = address;
	/* Read the byte. */
	EECR |= (1u << EERE);
	if (EEDR == data) {
		/* The data in EEPROM matches the data to be written.
		 * No write is needed.
		 * This interrupt will trigger again immediately. */
	} else {
		/* Start programming of the byte.
		 * This interrupt will trigger again when programming finished. */
		EEDR = data;
		EECR |= (1u << EEMPE);
		EECR |= (1u << EEPE);
	}

	eep.ee_write_offset = ++offset;
	if (offset >= sizeof(struct eeprom_data)) {
		/* Done writing. Disable the interrupt. */
		EECR &= (uint8_t)~(1u << EERIE);

		/* Finalize write. Update shadow copy. */
		eep.shadow_copy = eep.work_copy;
		eep.store_running = false;
		eeprom_update_standby_suppress();
		dprintf("EEPROM write done.\r\n");
	}
}
#endif /* USE_EEPROM */

/* Start storing data to EEPROM.
 * Interrupts shall be disabled before calling this function. */
static void eeprom_trigger_store(void)
{
	if (USE_EEPROM) {
		if (memcmp(&eep.work_copy,
			   &eep.shadow_copy,
			   sizeof(eep.work_copy)) == 0) {
			/* The data did not change. */
			dprintf("EEPROM write not necessary.\r\n");
		} else {
			dprintf("EEPROM write start.\r\n");

			eep.store_running = true;

			/* Avoid standby during eeprom write. */
			eeprom_update_standby_suppress();

			/* Increment the serial number. This might wrap. */
			eep.work_copy.serial++;

			/* Increment the store index. */
			eep.ee_index = ring_next(eep.ee_index, EE_RING_MAX_INDEX);

			/* Reset the store byte offset. */
			eep.ee_write_offset = 0u;

			/* Enable the eeprom-ready interrupt.
			 * It will fire, if the EEPROM is ready. */
			EECR |= (1u << EERIE);
		}

		eep.store_request = false;
	}
}

/* Get the active dataset. */
struct eeprom_data * eeprom_get_data(void)
{
	if (USE_EEPROM)
		return &eep.work_copy;
	return NULL;
}

/* Schedule storing data to EEPROM. */
void eeprom_store_data(void)
{
	uint8_t irq_state;

	if (USE_EEPROM) {
		irq_state = irq_disable_save();

		eep.store_request = true;
		eep.store_timer_ms = EEPROM_STORE_DELAY_MS;

		irq_restore(irq_state);
	}
}

/* Handle wake up from deep sleep.
 * Called with interrupts disabled. */
void eeprom_handle_deep_sleep_wakeup(void)
{
	if (USE_EEPROM && USE_DEEP_SLEEP)
		eeprom_update_standby_suppress();
}

/* Watchdog timer interrupt service routine
 * for EEPROM handling.
 * Called with interrupts disabled. */
void eeprom_handle_watchdog_interrupt(void)
{
	if (USE_EEPROM) {
		if (eep.store_request && !eep.store_running) {
			eep.store_timer_ms = sub_sat_u16(eep.store_timer_ms,
							 watchdog_interval_ms());
			if (eep.store_timer_ms == 0u)
				eeprom_trigger_store();
		}
	}
}

/* Initialize EEPROM. */
void eeprom_init(void)
{
	uint8_t next_index, serial, next_serial;
	uint8_t found_index = 0u;

	if (!USE_EEPROM)
		return;

	build_assert(EE_RING_MAX_INDEX <= 0xFFu - 1u);

	/* Find the latest settings in the eeprom.
	 * The latest setting is the one with the largest
	 * index. However, wrap around must be considered. */
	serial = ee_read_byte(ptr_to_eeaddr(&eep_addrspace[0].serial));
	next_index = 0u;
	do {
		found_index = next_index;

		next_index = ring_next(next_index, EE_RING_MAX_INDEX);

		next_serial = ee_read_byte(ptr_to_eeaddr(&eep_addrspace[next_index].serial));
		if (next_serial != ((serial + 1u) & 0xFFu))
			break;

		serial = next_serial;
	} while (next_index != 0u);
	eep.ee_index = found_index;

	/* Read settings from EEPROM. */
	ee_read_block(&eep.work_copy,
		      ptr_to_eeaddr(&eep_addrspace[found_index]),
		      sizeof(eep.work_copy));
	eep.shadow_copy = eep.work_copy;

	eeprom_update_standby_suppress();
}
bues.ch cgit interface