/* * This file is part of GodMode9 * Copyright (C) 2019 Wolfvak * * 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, see . */ #include #include #include #include #include "common.h" #include "arm/timer.h" #include "hw/gpio.h" #include "hw/gpulcd.h" #include "hw/mcu.h" #include "system/event.h" #define MCUEV_HID_MASK ( \ MCUEV_HID_PWR_DOWN | MCUEV_HID_PWR_HOLD | \ MCUEV_HID_HOME_DOWN | MCUEV_HID_HOME_UP | MCUEV_HID_WIFI_SWITCH) enum { MCUREG_VOLUME_SLIDER = 0x09, MCUREG_BATTERY_LEVEL = 0x0B, MCUREG_CONSOLE_STATE = 0x0F, MCUREG_INT_MASK = 0x10, MCUREG_INT_EN = 0x18, MCUREG_LCD_STATE = 0x22, MCUREG_LED_WIFI = 0x2A, MCUREG_LED_CAMERA = 0x2B, MCUREG_LED_SLIDER = 0x2C, MCUREG_LED_STATUS = 0x2D, MCUREG_RTC = 0x30, }; typedef struct { u8 delay; u8 smoothing; u8 loop_delay; u8 unk; u32 red[8]; u32 green[8]; u32 blue[8]; } PACKED_STRUCT mcuStatusLED; static u8 volumeSliderValue; static u32 shellState; static _Atomic(u32) pendingEvents; static void mcuEventUpdate(void) { u32 mask; // lazily update the mask on each test attempt if (!getEventIRQ()->test(MCU_INTERRUPT, true)) return; // reading the pending mask automagically acknowledges // the interrupts so all of them must be processed in one go mcuReadRegBuf(MCUREG_INT_MASK, (u8*)&mask, sizeof(mask)); if (mask & MCUEV_HID_VOLUME_SLIDER) volumeSliderValue = mcuReadReg(MCUREG_VOLUME_SLIDER); if (mask & MCUEV_HID_SHELL_OPEN) { mcuResetLEDs(); shellState = SHELL_OPEN; } if (mask & MCUEV_HID_SHELL_CLOSE) { shellState = SHELL_CLOSED; } atomic_fetch_or(&pendingEvents, mask); } u8 mcuGetVolumeSlider(void) { mcuEventUpdate(); return volumeSliderValue; } u32 mcuGetSpecialHID(void) { u32 ret = 0, pend = getEventMCU()->test(MCUEV_HID_MASK, MCUEV_HID_MASK); // hopefully gets unrolled if (pend & (MCUEV_HID_PWR_DOWN | MCUEV_HID_PWR_HOLD)) ret |= BUTTON_POWER; if (pend & MCUEV_HID_HOME_DOWN) ret |= BUTTON_HOME; if (pend & MCUEV_HID_HOME_UP) ret &= ~BUTTON_HOME; return ret | shellState; } void mcuSetStatusLED(u32 period_ms, u32 color) { u32 r, g, b, delay; mcuStatusLED ledState; delay = clamp((period_ms * 0x200) / 1000, 1, 0xFF); ledState.delay = delay; ledState.smoothing = delay; ledState.loop_delay = 0x10; ledState.unk = 0; // all colors look like 0x00ZZ00ZZ // in order to alternate between // LED "off" and the wanted color r = (color >> 16) & 0xFF; r |= r << 16; for (int i = 0; i < 8; i++) ledState.red[i] = r; g = (color >> 8) & 0xFF; g |= g << 16; for (int i = 0; i < 8; i++) ledState.green[i] = g; b = color & 0xFF; b |= b << 16; for (int i = 0; i < 8; i++) ledState.blue[i] = b; mcuWriteRegBuf(MCUREG_LED_STATUS, (const u8*)&ledState, sizeof(ledState)); } void mcuResetLEDs(void) { mcuWriteReg(MCUREG_LED_WIFI, 0); mcuWriteReg(MCUREG_LED_CAMERA, 0); mcuWriteReg(MCUREG_LED_SLIDER, 0); mcuSetStatusLED(0, 0); } void mcuReset(void) { u32 intmask = 0; atomic_init(&pendingEvents, 0); // set register mask and clear any pending registers mcuWriteRegBuf(MCUREG_INT_EN, (const u8*)&intmask, sizeof(intmask)); mcuReadRegBuf(MCUREG_INT_MASK, (u8*)&intmask, sizeof(intmask)); mcuResetLEDs(); volumeSliderValue = mcuReadReg(MCUREG_VOLUME_SLIDER); shellState = SHELL_OPEN; // assume the shell is always open on boot // knowing the average 3DS user, there will be plenty // of laughs when this comes back to bite us in the rear GPIO_setBit(19, 9); } static void evReset(void) { atomic_store(&pendingEvents, 0); } static u32 evTest(u32 mask, u32 clear) { mcuEventUpdate(); return atomic_fetch_and(&pendingEvents, ~clear) & mask; } static const EventInterface evMCU = { .reset = evReset, .test = evTest }; const EventInterface *getEventMCU(void) { return &evMCU; }