pmic.c 7.96 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>
19
#include <string.h>
20 21 22 23

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

24 25 26 27 28 29 30
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,
};

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

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

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

53 54
	hwlock_acquire(HWLOCK_ADC);
	hwlock_acquire(HWLOCK_I2C);
Rahix's avatar
Rahix committed
55 56 57 58 59 60 61 62 63 64

	/* 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);
65

Rahix's avatar
Rahix committed
66
	vTaskDelay(pdMS_TO_TICKS(5));
67
	hwlock_acquire(HWLOCK_I2C);
Rahix's avatar
Rahix committed
68 69 70 71 72 73

	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.  */
74
	MAX77650_setMUX_SEL(PMIC_AMUX_DISABLED);
Rahix's avatar
Rahix committed
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

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

108 109
	hwlock_release(HWLOCK_I2C);
	hwlock_release(HWLOCK_ADC);
110

Rahix's avatar
Rahix committed
111 112 113
	return ret;
}

114 115 116 117
/*
 * Read the interrupt flag register and handle all interrupts which the PMIC has
 * sent.  In most cases this will be the buttons.
 */
118
static uint8_t pmic_poll_interrupts(void)
119
{
120
	hwlock_acquire(HWLOCK_I2C);
121 122 123 124 125 126 127 128

	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);
	}
129 130

	return int_flag;
131 132
}

133 134 135 136 137 138 139 140
__attribute__((noreturn)) static void pmic_die(float u_batt)
{
	/* Stop core 1 */
	core1_stop();

	/* Grab the screen */
	disp_forcelock();

141 142 143
	/* Turn it on in case it was off */
	epic_disp_backlight(100);

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
	/* 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();

159 160 161
	/* We have some of headroom to keep the RTC going.
	 * The battery protection circuit will shut down
	 * the system at 3.0 V */
162

163 164 165
	/* TODO: Wake-up when USB is attached again */
	sleep_deepsleep();
	card10_reset();
166 167
}

168 169 170 171 172 173 174 175 176 177
/*
 * 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) {
178 179 180 181
		LOG_ERR("pmic",
			"Failed reading battery voltage: %s (%d)",
			strerror(-res),
			res);
182 183 184 185 186 187 188 189 190 191
		return;
	}

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

192 193 194
	if (u_batt < BATTERY_CRITICAL) {
		pmic_die(u_batt);
	}
195 196
}

197 198 199 200 201 202 203 204
/*
 * API-call for battery voltage
 */
int epic_read_battery_voltage(float *result)
{
	return pmic_read_amux(PMIC_AMUX_BATT_U, result);
}

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

249 250 251 252 253 254 255 256 257
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);
}

258 259
void vPmicTask(void *pvParameters)
{
260 261 262
	pmic_task_id       = xTaskGetCurrentTaskHandle();
	uint8_t interrupts = 0;
	uint32_t reason    = 0;
263

Rahix's avatar
Rahix committed
264 265 266
	ADC_Init(0x9, NULL);
	GPIO_Config(&gpio_cfg_adc0);

267
	TickType_t button_start_tick = 0;
268

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
	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);

285 286
	/* Clear all pending interrupts. */
	pmic_poll_interrupts();
287

288
	while (1) {
289 290 291
		interrupts |= pmic_poll_interrupts();
		if (interrupts == 0) {
			reason |= ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
292 293
		}

294 295 296 297 298
		/* New interrupts */
		if (reason & PMIC_NOTIFY_IRQ) {
			reason ^= PMIC_NOTIFY_IRQ;
			interrupts |= pmic_poll_interrupts();
		}
299

300 301 302 303 304
		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. */
305
		}
306

307 308 309 310 311 312 313 314 315 316 317
		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();
318 319 320 321

					/* Turn it on in case it was off */
					epic_disp_backlight(100);

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
					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)) {
352
						serial_return_to_synchronous();
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
						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)) {
371
						serial_return_to_synchronous();
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
						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();
				}
			}
388 389
		}

390
		if (reason & PMIC_NOTIFY_MONITOR) {
391
			reason ^= PMIC_NOTIFY_MONITOR;
392
			pmic_check_battery();
393 394 395
		}
	}
}