pmic.c 8.22 KB
Newer Older
Rahix's avatar
Rahix committed
1
2
3
#include "epicardium.h"
#include "modules/modules.h"
#include "modules/log.h"
4

Rahix's avatar
Rahix committed
5
#include "card10.h"
6
7
#include "pmic.h"
#include "MAX77650-Arduino-Library.h"
Rahix's avatar
Rahix committed
8
9
10
11
12

#include "max32665.h"
#include "mxc_sys.h"
#include "mxc_pins.h"
#include "adc.h"
13
14
15

#include "FreeRTOS.h"
#include "task.h"
16
#include "timers.h"
17

Rahix's avatar
Rahix committed
18
#include <stdio.h>
swym's avatar
swym committed
19
#include <string.h>
20

21
22
#define LOCK_WAIT pdMS_TO_TICKS(1000)

23
24
25
/* Task ID for the pmic handler */
static TaskHandle_t pmic_task_id = NULL;

26
27
28
29
30
31
32
enum {
	/* An irq was received, probably the power button */
	PMIC_NOTIFY_IRQ = 1,
	/* The timer has ticked and we should check the battery voltage again */
	PMIC_NOTIFY_MONITOR = 2,
};

33
34
35
36
void pmic_interrupt_callback(void *_)
{
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
	if (pmic_task_id != NULL) {
37
38
39
40
41
42
		xTaskNotifyFromISR(
			pmic_task_id,
			PMIC_NOTIFY_IRQ,
			eSetBits,
			&xHigherPriorityTaskWoken
		);
43
44
45
46
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
	}
}

Rahix's avatar
Rahix committed
47
48
int pmic_read_amux(enum pmic_amux_signal sig, float *result)
{
49
50
	int ret     = 0;
	int i2c_ret = 0;
Rahix's avatar
Rahix committed
51
52
53
54
55

	if (sig > _PMIC_AMUX_MAX) {
		return -EINVAL;
	}

56
	int adc_ret = hwlock_acquire(HWLOCK_ADC, LOCK_WAIT);
57
58
59
	if (adc_ret < 0) {
		ret = adc_ret;
		goto done;
Rahix's avatar
Rahix committed
60
	}
61
	i2c_ret = hwlock_acquire(HWLOCK_I2C, LOCK_WAIT);
62
63
64
	if (i2c_ret < 0) {
		ret = i2c_ret;
		goto done;
Rahix's avatar
Rahix committed
65
66
67
68
69
70
71
72
73
74
75
	}

	/* Select the correct channel for this measurement.  */
	MAX77650_setMUX_SEL(sig);

	/*
	 * According to the datasheet, the voltage will stabilize within 0.3us.
	 * Just to be sure, we'll wait a little longer.  In the meantime,
	 * release the I2C mutex.
	 */
	hwlock_release(HWLOCK_I2C);
76

Rahix's avatar
Rahix committed
77
	vTaskDelay(pdMS_TO_TICKS(5));
78
	i2c_ret = hwlock_acquire(HWLOCK_I2C, LOCK_WAIT);
79
80
81
	if (i2c_ret < 0) {
		ret = i2c_ret;
		goto done;
Rahix's avatar
Rahix committed
82
83
84
85
86
87
88
	}

	uint16_t adc_data;
	ADC_StartConvert(ADC_CH_0, 0, 0);
	ADC_GetData(&adc_data);

	/* Turn MUX back to neutral so it does not waste power.  */
89
	MAX77650_setMUX_SEL(PMIC_AMUX_DISABLED);
Rahix's avatar
Rahix committed
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

	/* Convert ADC measurement to SI Volts */
	float adc_voltage = (float)adc_data / 1023.0f * 1.22f;

	/*
	 * Convert value according to PMIC formulas (Table 7)
	 */
	switch (sig) {
	case PMIC_AMUX_CHGIN_U:
		*result = adc_voltage / 0.167f;
		break;
	case PMIC_AMUX_CHGIN_I:
		*result = adc_voltage / 2.632f;
		break;
	case PMIC_AMUX_BATT_U:
		*result = adc_voltage / 0.272f;
		break;
	case PMIC_AMUX_BATT_CHG_I:
		*result = adc_voltage / 1.25f;
		break;
	case PMIC_AMUX_BATT_NULL_I:
	case PMIC_AMUX_THM_U:
	case PMIC_AMUX_TBIAS_U:
	case PMIC_AMUX_AGND_U:
		*result = adc_voltage;
		break;
	case PMIC_AMUX_SYS_U:
		*result = adc_voltage / 0.26f;
		break;
	default:
		ret = -EINVAL;
	}

123
124
125
126
127
128
129
130
131
done:
	if (i2c_ret == 0) {
		hwlock_release(HWLOCK_I2C);
	}

	if (adc_ret == 0) {
		hwlock_release(HWLOCK_ADC);
	}

Rahix's avatar
Rahix committed
132
133
134
	return ret;
}

135
136
137
138
/*
 * Read the interrupt flag register and handle all interrupts which the PMIC has
 * sent.  In most cases this will be the buttons.
 */
139
static uint8_t pmic_poll_interrupts(void)
140
{
141
	while (hwlock_acquire(HWLOCK_I2C, LOCK_WAIT) < 0) {
142
		LOG_WARN("pmic", "Failed to acquire I2C. Retrying ...");
143
		vTaskDelay(pdMS_TO_TICKS(100));
144
145
146
147
148
149
150
151
152
	}

	uint8_t int_flag = MAX77650_getINT_GLBL();
	hwlock_release(HWLOCK_I2C);

	/* TODO: Remove when all interrupts are handled */
	if (int_flag & ~(MAX77650_INT_nEN_F | MAX77650_INT_nEN_R)) {
		LOG_WARN("pmic", "Unhandled PMIC Interrupt: %x", int_flag);
	}
153
154

	return int_flag;
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
__attribute__((noreturn)) static void pmic_die(float u_batt)
{
	/* Stop core 1 */
	core1_stop();

	/* Grab the screen */
	disp_forcelock();

	/* Draw an error screen */
	epic_disp_clear(0x0000);

	epic_disp_print(0, 0, " Battery", 0xffff, 0x0000);
	epic_disp_print(0, 20, " critical", 0xffff, 0x0000);
	epic_disp_print(0, 40, "  !!!!", 0xffff, 0x0000);
	epic_disp_update();

	/* Vibrate violently */
	epic_vibra_set(true);

	/* Wait a bit */
	for (int i = 0; i < 50000000; i++)
		__NOP();

180
181
182
	/* We have some of headroom to keep the RTC going.
	 * The battery protection circuit will shut down
	 * the system at 3.0 V */
183

184
185
186
	/* TODO: Wake-up when USB is attached again */
	sleep_deepsleep();
	card10_reset();
187
	while (1)
188
		;
189
190
}

191
192
193
194
195
196
197
198
199
200
/*
 * Check the battery voltage.  If it drops too low, turn card10 off.
 */
static void pmic_check_battery()
{
	float u_batt;
	int res;

	res = pmic_read_amux(PMIC_AMUX_BATT_U, &u_batt);
	if (res < 0) {
swym's avatar
swym committed
201
202
203
204
		LOG_ERR("pmic",
			"Failed reading battery voltage: %s (%d)",
			strerror(-res),
			res);
205
206
207
208
209
210
211
212
213
214
		return;
	}

	LOG_DEBUG(
		"pmic",
		"Battery is at %d.%03d V",
		(int)u_batt,
		(int)(u_batt * 1000.0) % 1000
	);

215
216
217
	if (u_batt < BATTERY_CRITICAL) {
		pmic_die(u_batt);
	}
218
219
}

220
221
222
223
224
225
226
227
/*
 * API-call for battery voltage
 */
int epic_read_battery_voltage(float *result)
{
	return pmic_read_amux(PMIC_AMUX_BATT_U, result);
}

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
/*
 * API-call for battery current
 */
int epic_read_battery_current(float *result)
{
	return pmic_read_amux(PMIC_AMUX_BATT_CHG_I, result);
}

/*
 * API-call for charge voltage
 */
int epic_read_chargein_voltage(float *result)
{
	return pmic_read_amux(PMIC_AMUX_CHGIN_U, result);
}

/*
 * API-call for charge voltage
 */
int epic_read_chargein_current(float *result)
{
	return pmic_read_amux(PMIC_AMUX_BATT_CHG_I, result);
}

/*
 * API-call for system voltage
 */
int epic_read_system_voltage(float *result)
{
	return pmic_read_amux(PMIC_AMUX_SYS_U, result);
}

/*
 * API-call for thermistor voltage
 *
 * Thermistor is as 10k at room temperature,
 * voltage divided with another 10k.
 * (50% V_bias at room temperature)
 */
int epic_read_thermistor_voltage(float *result)
{
	return pmic_read_amux(PMIC_AMUX_THM_U, result);
}

272
273
274
275
276
277
278
279
280
static StaticTimer_t pmic_timer_data;
static void vPmicTimerCb(TimerHandle_t xTimer)
{
	/*
	 * Tell the PMIC task to check the battery again.
	 */
	xTaskNotify(pmic_task_id, PMIC_NOTIFY_MONITOR, eSetBits);
}

281
282
void vPmicTask(void *pvParameters)
{
283
284
285
	pmic_task_id       = xTaskGetCurrentTaskHandle();
	uint8_t interrupts = 0;
	uint32_t reason    = 0;
286

Rahix's avatar
Rahix committed
287
288
289
	ADC_Init(0x9, NULL);
	GPIO_Config(&gpio_cfg_adc0);

290
	TickType_t button_start_tick = 0;
291

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
	pmic_check_battery();

	TimerHandle_t pmic_timer = xTimerCreateStatic(
		"PMIC Timer",
		pdMS_TO_TICKS(60 * 1000),
		pdTRUE,
		NULL,
		vPmicTimerCb,
		&pmic_timer_data
	);
	if (pmic_timer == NULL) {
		LOG_CRIT("pmic", "Could not create timer.");
		vTaskDelay(portMAX_DELAY);
	}
	xTimerStart(pmic_timer, 0);

308
309
	/* Clear all pending interrupts. */
	pmic_poll_interrupts();
310

311
	while (1) {
312
313
314
		interrupts |= pmic_poll_interrupts();
		if (interrupts == 0) {
			reason |= ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
315
316
		}

317
318
319
320
321
		/* New interrupts */
		if (reason & PMIC_NOTIFY_IRQ) {
			reason ^= PMIC_NOTIFY_IRQ;
			interrupts |= pmic_poll_interrupts();
		}
322

323
324
325
326
327
		if (interrupts & MAX77650_INT_nEN_R) {
			/* Ignored in this state */
			/* This can happen if the button is pressed
			 * during boot and released now. */
			interrupts ^= MAX77650_INT_nEN_R; /* Mark as handled. */
328
		}
329

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
		if (interrupts & MAX77650_INT_nEN_F) {
			/* Button was pressed */
			interrupts ^= MAX77650_INT_nEN_F; /* Mark as handled. */

			button_start_tick = xTaskGetTickCount();
			while (true) {
				TickType_t duration =
					xTaskGetTickCount() - button_start_tick;

				if (duration > 1000) {
					disp_forcelock();
					epic_disp_clear(0x0000);

					char buf[20];
					sprintf(buf,
						"Off in %d",
						7 - (int)(duration + 500) /
								1000);
					epic_disp_print(
						0,
						0,
						"Sleep zZz..",
						0xffff,
						0x0000
					);
					epic_disp_print(
						0, 25, buf, 0xf000, 0x0000
					);
					epic_disp_print(
						0,
						50,
						"   Reset ->",
						0xffff,
						0x0000
					);
					epic_disp_update();
				}

				if (duration >= pdMS_TO_TICKS(1000)) {
					if (epic_buttons_read(
						    BUTTON_RIGHT_TOP)) {
						LOG_WARN(
							"pmic",
							"Resetting ..."
						);
						card10_reset();
					}
				}

				if (interrupts & MAX77650_INT_nEN_R) {
					/* Button is released */
					interrupts ^=
						MAX77650_INT_nEN_R; /* Mark as handled. */

					if (duration < pdMS_TO_TICKS(1000)) {
						return_to_menu();
					}

					if (duration > pdMS_TO_TICKS(1000)) {
						LOG_WARN("pmic", "Poweroff");
						sleep_deepsleep();
						card10_reset();
					}
					break;
				}

				reason |= ulTaskNotifyTake(
					pdTRUE, pdMS_TO_TICKS(200)
				);
				if (reason & PMIC_NOTIFY_IRQ) {
					/* New interrupts */
					reason ^= PMIC_NOTIFY_IRQ;
					interrupts |= pmic_poll_interrupts();
				}
			}
405
406
		}

407
		if (reason & PMIC_NOTIFY_MONITOR) {
408
			reason ^= PMIC_NOTIFY_MONITOR;
409
			pmic_check_battery();
410
411
412
		}
	}
}