Fix a few MCU-related bugs, gm9 should boot properly now
- Stop spamming mcu reads... use the GPIO pin to know if there's a MCU interrupt, instead - Wait for the MCU interrupts to be raised when powering up LCD+backlights - Also turn off LCD+backlight when needed
This commit is contained in:
parent
b5b5db6e8c
commit
36ea48b4dc
@ -123,8 +123,8 @@ static void initScreens(u32 brightnessLevel, struct fb *fbs)
|
|||||||
for(u32 i = 0; i < 256; i++)
|
for(u32 i = 0; i < 256; i++)
|
||||||
*(vu32 *)0x10400584 = 0x10101 * i;
|
*(vu32 *)0x10400584 = 0x10101 * i;
|
||||||
|
|
||||||
*(vu32 *)0x10202204 = 0x00000000; //unset LCD fill
|
//*(vu32 *)0x10202204 = 0x00000000; //unset LCD fill
|
||||||
*(vu32 *)0x10202A04 = 0x00000000;
|
//*(vu32 *)0x10202A04 = 0x00000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setupFramebuffers(struct fb *fbs)
|
static void setupFramebuffers(struct fb *fbs)
|
||||||
|
@ -45,16 +45,19 @@ SECTIONS
|
|||||||
|
|
||||||
chainloader.o(.text*)
|
chainloader.o(.text*)
|
||||||
i2c.o(.text*)
|
i2c.o(.text*)
|
||||||
|
mcu.o(.text*)
|
||||||
arm9_exception_handlers.o(.text*)
|
arm9_exception_handlers.o(.text*)
|
||||||
|
|
||||||
*(.arm9_exception_handlers.rodata*)
|
*(.arm9_exception_handlers.rodata*)
|
||||||
chainloader.o(.rodata*)
|
chainloader.o(.rodata*)
|
||||||
i2c.o(.rodata*)
|
i2c.o(.rodata*)
|
||||||
|
mcu.o(.rodata*)
|
||||||
arm9_exception_handlers.o(.rodata*)
|
arm9_exception_handlers.o(.rodata*)
|
||||||
|
|
||||||
*(.arm9_exception_handlers.data*)
|
*(.arm9_exception_handlers.data*)
|
||||||
chainloader.o(.data*)
|
chainloader.o(.data*)
|
||||||
i2c.o(.data*)
|
i2c.o(.data*)
|
||||||
|
mcu.o(.data*)
|
||||||
arm9_exception_handlers.o(.data*)
|
arm9_exception_handlers.o(.data*)
|
||||||
. = ALIGN(32);
|
. = ALIGN(32);
|
||||||
} >itcm AT>main :itcm
|
} >itcm AT>main :itcm
|
||||||
@ -67,6 +70,7 @@ SECTIONS
|
|||||||
*(.arm9_exception_handlers.bss*)
|
*(.arm9_exception_handlers.bss*)
|
||||||
chainloader.o(.bss* COMMON)
|
chainloader.o(.bss* COMMON)
|
||||||
i2c.o(.bss* COMMON)
|
i2c.o(.bss* COMMON)
|
||||||
|
mcu.o(.bss* COMMON)
|
||||||
arm9_exception_handlers.o(.bss* COMMON)
|
arm9_exception_handlers.o(.bss* COMMON)
|
||||||
. = ALIGN(32);
|
. = ALIGN(32);
|
||||||
PROVIDE (__itcm_end__ = ABSOLUTE(.));
|
PROVIDE (__itcm_end__ = ABSOLUTE(.));
|
||||||
|
@ -25,8 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "arm9_exception_handlers.h"
|
#include "arm9_exception_handlers.h"
|
||||||
#include "i2c.h"
|
#include "mcu.h"
|
||||||
#include "screen.h"
|
|
||||||
|
|
||||||
#define FINAL_BUFFER 0x25000000
|
#define FINAL_BUFFER 0x25000000
|
||||||
|
|
||||||
@ -103,10 +102,9 @@ void __attribute__((noreturn)) arm9ExceptionHandlerMain(u32 *registerDump, u32 t
|
|||||||
//Copy header (actually optimized by the compiler)
|
//Copy header (actually optimized by the compiler)
|
||||||
*(ExceptionDumpHeader *)FINAL_BUFFER = dumpHeader;
|
*(ExceptionDumpHeader *)FINAL_BUFFER = dumpHeader;
|
||||||
|
|
||||||
if(ARESCREENSINITIALIZED) I2C_writeReg(I2C_DEV_MCU, 0x22, 1 << 0); //Shutdown LCD
|
mcuPowerBacklightsOff();
|
||||||
|
|
||||||
((void (*)())0xFFFF0830)(); //Ensure that all memory transfers have completed and that the data cache has been flushed
|
((void (*)())0xFFFF0830)(); //Ensure that all memory transfers have completed and that the data cache has been flushed
|
||||||
|
|
||||||
I2C_writeReg(I2C_DEV_MCU, 0x20, 1 << 2); //Reboot
|
mcuReboot();
|
||||||
while(true);
|
|
||||||
}
|
}
|
||||||
|
@ -200,5 +200,5 @@ void detectAndProcessExceptionDumps(void)
|
|||||||
|
|
||||||
exit:
|
exit:
|
||||||
memset((void *)dumpHeader, 0, dumpHeader->totalSize);
|
memset((void *)dumpHeader, 0, dumpHeader->totalSize);
|
||||||
mcuPowerOff();
|
powerOff();
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "fmt.h"
|
#include "fmt.h"
|
||||||
#include "chainloader.h"
|
#include "chainloader.h"
|
||||||
|
#include "mcu.h"
|
||||||
|
|
||||||
static Firm *firm = (Firm *)0x20001000;
|
static Firm *firm = (Firm *)0x20001000;
|
||||||
|
|
||||||
@ -564,6 +565,7 @@ u32 patch1x2xNativeAndSafeFirm(void)
|
|||||||
|
|
||||||
void launchFirm(int argc, char **argv)
|
void launchFirm(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
mcuFinalize();
|
||||||
prepareArm11ForFirmlaunch();
|
prepareArm11ForFirmlaunch();
|
||||||
chainload(argc, argv, firm);
|
chainload(argc, argv, firm);
|
||||||
}
|
}
|
||||||
|
225
arm9/source/gpio.h
Normal file
225
arm9/source/gpio.h
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Luma3DS
|
||||||
|
* Copyright (C) 2021 Aurora Wright, TuxSH
|
||||||
|
*
|
||||||
|
* 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 3 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
|
||||||
|
* * Requiring preservation of specified reasonable legal notices or
|
||||||
|
* author attributions in that material or in the Appropriate Legal
|
||||||
|
* Notices displayed by works containing it.
|
||||||
|
* * Prohibiting misrepresentation of the origin of that material,
|
||||||
|
* or requiring that modified versions of such material be marked in
|
||||||
|
* reasonable ways as different from the original version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
typedef struct GpioRegisters {
|
||||||
|
u8 gpio1_data;
|
||||||
|
u8 _0x001[0x010 - 0x001];
|
||||||
|
|
||||||
|
u8 gpio2_data;
|
||||||
|
u8 gpio2_dir;
|
||||||
|
u8 gpio2_intcfg;
|
||||||
|
u8 gpio2_inten;
|
||||||
|
u16 gpio2_data2;
|
||||||
|
u8 _0x016[0x020 - 0x016];
|
||||||
|
|
||||||
|
u16 gpio3_data;
|
||||||
|
u16 gpio3_dir;
|
||||||
|
u16 gpio3_intcfg;
|
||||||
|
u16 gpio3_inten;
|
||||||
|
u16 gpio3_data2;
|
||||||
|
u8 _0x02a[0x100 - 0x02A];
|
||||||
|
} GpioRegisters;
|
||||||
|
|
||||||
|
typedef enum GpioDirection {
|
||||||
|
GPIO_DIR_INPUT = 0,
|
||||||
|
GPIO_DIR_OUTPUT = 1,
|
||||||
|
} GpioDirection;
|
||||||
|
|
||||||
|
typedef enum GpioInterruptConfig {
|
||||||
|
GPIO_INTCFG_FALLING_EDGE = 0,
|
||||||
|
GPIO_INTCFG_RISING_EDGE = 1,
|
||||||
|
} GpioInterruptConfig;
|
||||||
|
|
||||||
|
#define GPIO_PIN(bank, idx) (((bank) << 4) | ((idx) & 0xF))
|
||||||
|
#define BANK_OF(pin) ((pin) >> 4)
|
||||||
|
#define BIT_OF(pin) ((pin) & 0xF)
|
||||||
|
|
||||||
|
typedef enum GpioPin {
|
||||||
|
// GPIO1
|
||||||
|
GPIO_DEBUG_BUTTON = GPIO_PIN(1, 0), // active-low
|
||||||
|
GPIO_TOUCH_SCREEN = GPIO_PIN(1, 1), // active-low, 0 when the touch screen is pressed
|
||||||
|
GPIO_SHELL_CLOSED = GPIO_PIN(1, 2),
|
||||||
|
|
||||||
|
// GPIO2
|
||||||
|
GPIO_HEADPHONES_INSERTED = GPIO_PIN(2, 0),
|
||||||
|
GPIO_TWL_DEPOP = GPIO_PIN(2, 1), // active-low
|
||||||
|
|
||||||
|
// GPIO2 (DATA2)
|
||||||
|
GPIO_WIFI_MODE = GPIO_PIN(4, 0), // 0 is CTR, 1 is MP (DS WiFi)
|
||||||
|
|
||||||
|
// GPIO3
|
||||||
|
GPIO_CSTICK_INT = GPIO_PIN(3, 0),
|
||||||
|
GPIO_IRDA_INT = GPIO_PIN(3, 1), // active-low
|
||||||
|
GPIO_GYRO_INT = GPIO_PIN(3, 2),
|
||||||
|
GPIO_CSTICK_STOP = GPIO_PIN(3, 3), // output
|
||||||
|
GPIO_IRDA_TXRC = GPIO_PIN(3, 4), // output
|
||||||
|
GPIO_IRDA_RXD = GPIO_PIN(3, 5), // active-low
|
||||||
|
GPIO_NFC_OUT1 = GPIO_PIN(3, 6), // output
|
||||||
|
GPIO_NFC_OUT2 = GPIO_PIN(3, 7), // output
|
||||||
|
GPIO_HEADPHONES_BUTTON = GPIO_PIN(3, 8), // active-low ("half-inserted")
|
||||||
|
GPIO_MCU_INT = GPIO_PIN(3, 9),
|
||||||
|
GPIO_NFC_INT = GPIO_PIN(3, 10),
|
||||||
|
GPIO_QTM_OUT = GPIO_PIN(3, 11), // output
|
||||||
|
|
||||||
|
// GPIO3 (DATA2)
|
||||||
|
GPIO_WIFI_ENABLED = GPIO_PIN(5, 0),
|
||||||
|
} GpioPin;
|
||||||
|
|
||||||
|
static volatile GpioRegisters *const GPIO = (volatile GpioRegisters *)0x10147000;
|
||||||
|
|
||||||
|
static inline bool gpioRead(GpioPin pin)
|
||||||
|
{
|
||||||
|
u32 bank = BANK_OF(pin);
|
||||||
|
u32 bit = BIT_OF(pin);
|
||||||
|
|
||||||
|
switch (bank) {
|
||||||
|
case 1:
|
||||||
|
return (GPIO->gpio1_data & BIT(bit)) != 0;
|
||||||
|
case 2:
|
||||||
|
return (GPIO->gpio2_data & BIT(bit)) != 0;
|
||||||
|
case 3:
|
||||||
|
return (GPIO->gpio3_data & BIT(bit)) != 0;
|
||||||
|
case 4:
|
||||||
|
return (GPIO->gpio2_data2 & BIT(bit)) != 0;
|
||||||
|
case 5:
|
||||||
|
return (GPIO->gpio3_data2 & BIT(bit)) != 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void gpioWrite(GpioPin pin, bool val)
|
||||||
|
{
|
||||||
|
u32 bank = BANK_OF(pin);
|
||||||
|
u32 bit = BIT_OF(pin);
|
||||||
|
|
||||||
|
u32 valMask = (val ? 1 : 0) << bit;
|
||||||
|
u32 tmp;
|
||||||
|
|
||||||
|
switch (bank) {
|
||||||
|
case 1:
|
||||||
|
tmp = GPIO->gpio1_data & ~BIT(bit);
|
||||||
|
GPIO->gpio1_data = (u8)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
tmp = GPIO->gpio2_data & ~BIT(bit);
|
||||||
|
GPIO->gpio2_data = (u8)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
tmp = GPIO->gpio3_data & ~BIT(bit);
|
||||||
|
GPIO->gpio3_data = (u16)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
tmp = GPIO->gpio2_data2 & ~BIT(bit);
|
||||||
|
GPIO->gpio2_data2 = (u16)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
tmp = GPIO->gpio3_data2 & ~BIT(bit);
|
||||||
|
GPIO->gpio3_data2 = (u16)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void gpioSetDirection(GpioPin pin, GpioDirection direction)
|
||||||
|
{
|
||||||
|
u32 bank = BANK_OF(pin);
|
||||||
|
u32 bit = BIT_OF(pin);
|
||||||
|
|
||||||
|
u32 valMask = (direction == GPIO_DIR_OUTPUT ? 1 : 0) << bit;
|
||||||
|
u32 tmp;
|
||||||
|
|
||||||
|
switch (bank) {
|
||||||
|
case 2:
|
||||||
|
tmp = GPIO->gpio2_dir & ~BIT(bit);
|
||||||
|
GPIO->gpio2_dir = (u8)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
tmp = GPIO->gpio3_dir & ~BIT(bit);
|
||||||
|
GPIO->gpio3_dir = (u16)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void gpioConfigureInterrupt(GpioPin pin, GpioInterruptConfig cfg)
|
||||||
|
{
|
||||||
|
u32 bank = BANK_OF(pin);
|
||||||
|
u32 bit = BIT_OF(pin);
|
||||||
|
|
||||||
|
u32 valMask = (cfg == GPIO_INTCFG_RISING_EDGE ? 1 : 0) << bit;
|
||||||
|
u32 tmp;
|
||||||
|
|
||||||
|
switch (bank) {
|
||||||
|
case 2:
|
||||||
|
tmp = GPIO->gpio2_intcfg & ~BIT(bit);
|
||||||
|
GPIO->gpio2_intcfg = (u8)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
tmp = GPIO->gpio3_data & ~BIT(bit);
|
||||||
|
GPIO->gpio3_intcfg = (u16)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void gpioSetInterruptEnabled(GpioPin pin, bool enabled)
|
||||||
|
{
|
||||||
|
u32 bank = BANK_OF(pin);
|
||||||
|
u32 bit = BIT_OF(pin);
|
||||||
|
|
||||||
|
u32 valMask = (enabled ? 1 : 0) << bit;
|
||||||
|
u32 tmp;
|
||||||
|
|
||||||
|
switch (bank) {
|
||||||
|
case 2:
|
||||||
|
tmp = GPIO->gpio2_inten & ~BIT(bit);
|
||||||
|
GPIO->gpio2_inten = (u8)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
tmp = GPIO->gpio3_inten & ~BIT(bit);
|
||||||
|
GPIO->gpio3_inten = (u16)(tmp | valMask);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef GPIO_PIN
|
||||||
|
#undef BIT_OF
|
||||||
|
#undef BANK_OF
|
@ -16,7 +16,10 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Modified 2021 TuxSH
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@ -158,11 +161,11 @@ static bool i2cStartTransfer(I2cDevice devId, u8 regAddr, bool read, I2cRegs *co
|
|||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, u8 *out, u32 size)
|
bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, void *out, u32 size)
|
||||||
{
|
{
|
||||||
const u8 busId = i2cDevTable[devId].busId;
|
const u8 busId = i2cDevTable[devId].busId;
|
||||||
I2cRegs *const regs = i2cGetBusRegsBase(busId);
|
I2cRegs *const regs = i2cGetBusRegsBase(busId);
|
||||||
|
u8 *out8 = (u8 *)out;
|
||||||
|
|
||||||
if(!i2cStartTransfer(devId, regAddr, true, regs)) return false;
|
if(!i2cStartTransfer(devId, regAddr, true, regs)) return false;
|
||||||
|
|
||||||
@ -170,27 +173,28 @@ bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, u8 *out, u32 size)
|
|||||||
{
|
{
|
||||||
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_READ | I2C_ACK;
|
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_READ | I2C_ACK;
|
||||||
i2cWaitBusy(regs);
|
i2cWaitBusy(regs);
|
||||||
*out++ = regs->REG_I2C_DATA;
|
*out8++ = regs->REG_I2C_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_READ | I2C_STOP;
|
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_READ | I2C_STOP;
|
||||||
i2cWaitBusy(regs);
|
i2cWaitBusy(regs);
|
||||||
*out = regs->REG_I2C_DATA; // Last byte
|
*out8 = regs->REG_I2C_DATA; // Last byte
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool I2C_writeRegBuf(I2cDevice devId, u8 regAddr, const u8 *in, u32 size)
|
bool I2C_writeRegBuf(I2cDevice devId, u8 regAddr, const void *in, u32 size)
|
||||||
{
|
{
|
||||||
const u8 busId = i2cDevTable[devId].busId;
|
const u8 busId = i2cDevTable[devId].busId;
|
||||||
I2cRegs *const regs = i2cGetBusRegsBase(busId);
|
I2cRegs *const regs = i2cGetBusRegsBase(busId);
|
||||||
|
const u8 *in8 = (const u8 *)in;
|
||||||
|
|
||||||
|
|
||||||
if(!i2cStartTransfer(devId, regAddr, false, regs)) return false;
|
if(!i2cStartTransfer(devId, regAddr, false, regs)) return false;
|
||||||
|
|
||||||
while(--size)
|
while(--size)
|
||||||
{
|
{
|
||||||
regs->REG_I2C_DATA = *in++;
|
regs->REG_I2C_DATA = *in8++;
|
||||||
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_WRITE;
|
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_WRITE;
|
||||||
i2cWaitBusy(regs);
|
i2cWaitBusy(regs);
|
||||||
if(!I2C_GET_ACK(regs->REG_I2C_CNT)) // If ack flag is 0 it failed.
|
if(!I2C_GET_ACK(regs->REG_I2C_CNT)) // If ack flag is 0 it failed.
|
||||||
@ -200,7 +204,7 @@ bool I2C_writeRegBuf(I2cDevice devId, u8 regAddr, const u8 *in, u32 size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
regs->REG_I2C_DATA = *in;
|
regs->REG_I2C_DATA = *in8;
|
||||||
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_WRITE | I2C_STOP;
|
regs->REG_I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIRE_WRITE | I2C_STOP;
|
||||||
i2cWaitBusy(regs);
|
i2cWaitBusy(regs);
|
||||||
if(!I2C_GET_ACK(regs->REG_I2C_CNT)) // If ack flag is 0 it failed.
|
if(!I2C_GET_ACK(regs->REG_I2C_CNT)) // If ack flag is 0 it failed.
|
||||||
|
@ -66,7 +66,7 @@ void I2C_init(void);
|
|||||||
*
|
*
|
||||||
* @return Returns true on success and false on failure.
|
* @return Returns true on success and false on failure.
|
||||||
*/
|
*/
|
||||||
bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, u8 *out, u32 size);
|
bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, void *out, u32 size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Writes a buffer to a I2C register.
|
* @brief Writes a buffer to a I2C register.
|
||||||
@ -78,7 +78,7 @@ bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, u8 *out, u32 size);
|
|||||||
*
|
*
|
||||||
* @return Returns true on success and false on failure.
|
* @return Returns true on success and false on failure.
|
||||||
*/
|
*/
|
||||||
bool I2C_writeRegBuf(I2cDevice devId, u8 regAddr, const u8 *in, u32 size);
|
bool I2C_writeRegBuf(I2cDevice devId, u8 regAddr, const void *in, u32 size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reads a byte from a I2C register.
|
* @brief Reads a byte from a I2C register.
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
#include "crypto.h"
|
#include "crypto.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "i2c.h"
|
#include "mcu.h"
|
||||||
#include "fatfs/sdmmc/sdmmc.h"
|
#include "fatfs/sdmmc/sdmmc.h"
|
||||||
|
|
||||||
extern u8 __itcm_start__[], __itcm_lma__[], __itcm_bss_start__[], __itcm_end__[];
|
extern u8 __itcm_start__[], __itcm_lma__[], __itcm_bss_start__[], __itcm_end__[];
|
||||||
@ -116,7 +116,8 @@ void main(int argc, char **argv, u32 magicWord)
|
|||||||
// Set up the additional sections, overwrites argc
|
// Set up the additional sections, overwrites argc
|
||||||
memcpy(__itcm_start__, __itcm_lma__, __itcm_bss_start__ - __itcm_start__);
|
memcpy(__itcm_start__, __itcm_lma__, __itcm_bss_start__ - __itcm_start__);
|
||||||
memset(__itcm_bss_start__, 0, __itcm_end__ - __itcm_bss_start__);
|
memset(__itcm_bss_start__, 0, __itcm_end__ - __itcm_bss_start__);
|
||||||
I2C_init();
|
mcuInit();
|
||||||
|
|
||||||
if(isInvalidLoader) error("Launched using an unsupported loader.");
|
if(isInvalidLoader) error("Launched using an unsupported loader.");
|
||||||
|
|
||||||
installArm9Handlers();
|
installArm9Handlers();
|
||||||
@ -141,7 +142,7 @@ void main(int argc, char **argv, u32 magicWord)
|
|||||||
{
|
{
|
||||||
while(HID_PAD & NTRBOOT_BUTTONS);
|
while(HID_PAD & NTRBOOT_BUTTONS);
|
||||||
loadHomebrewFirm(0);
|
loadHomebrewFirm(0);
|
||||||
mcuPowerOff();
|
powerOff();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -164,7 +165,7 @@ void main(int argc, char **argv, u32 magicWord)
|
|||||||
//Determine if this is a firmlaunch boot
|
//Determine if this is a firmlaunch boot
|
||||||
if(bootType == FIRMLAUNCH)
|
if(bootType == FIRMLAUNCH)
|
||||||
{
|
{
|
||||||
if(needConfig == CREATE_CONFIGURATION) mcuPowerOff();
|
if(needConfig == CREATE_CONFIGURATION) powerOff();
|
||||||
|
|
||||||
switch(firmlaunchTidLow & 0xF)
|
switch(firmlaunchTidLow & 0xF)
|
||||||
{
|
{
|
||||||
|
127
arm9/source/mcu.c
Normal file
127
arm9/source/mcu.c
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Luma3DS
|
||||||
|
* Copyright (C) 2021 Aurora Wright, TuxSH
|
||||||
|
*
|
||||||
|
* 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 3 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
|
||||||
|
* * Requiring preservation of specified reasonable legal notices or
|
||||||
|
* author attributions in that material or in the Appropriate Legal
|
||||||
|
* Notices displayed by works containing it.
|
||||||
|
* * Prohibiting misrepresentation of the origin of that material,
|
||||||
|
* or requiring that modified versions of such material be marked in
|
||||||
|
* reasonable ways as different from the original version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mcu.h"
|
||||||
|
#include "i2c.h"
|
||||||
|
#include "gpio.h"
|
||||||
|
|
||||||
|
static u32 g_pendingMcuInterrupts = 0;
|
||||||
|
|
||||||
|
u32 mcuGetInterruptMask(void)
|
||||||
|
{
|
||||||
|
u32 mask;
|
||||||
|
I2C_readRegBuf(I2C_DEV_MCU, 0x18, &mask, 4);
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcuSetInterruptMask(u32 mask)
|
||||||
|
{
|
||||||
|
I2C_writeRegBuf(I2C_DEV_MCU, 0x18, &mask, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 mcuGetPendingInterrupts(u32 mask)
|
||||||
|
{
|
||||||
|
u32 curMcuInts = 0;
|
||||||
|
if (gpioRead(GPIO_MCU_INT))
|
||||||
|
{
|
||||||
|
// MCU IRQ pin raised
|
||||||
|
I2C_readRegBuf(I2C_DEV_MCU, 0x10, &curMcuInts, 4); // this clears the interrupts on the MCU side
|
||||||
|
|
||||||
|
// Add all new MCU interrupts to the pending list
|
||||||
|
g_pendingMcuInterrupts |= curMcuInts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the interrupts we'll return from the pending list
|
||||||
|
u32 ret = g_pendingMcuInterrupts & mask;
|
||||||
|
g_pendingMcuInterrupts &= ~mask;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcuPowerBacklightsOn(void)
|
||||||
|
{
|
||||||
|
// Doesn't matter if they're already on, it should be idempotent on the MCU side
|
||||||
|
|
||||||
|
u32 prevMask = mcuGetInterruptMask();
|
||||||
|
u32 pend = 0;
|
||||||
|
|
||||||
|
mcuSetInterruptMask(~MCU_INT_LCD_BL_ON);
|
||||||
|
mcuGetPendingInterrupts(MCU_INT_LCD_BL_ON) ; // Clear any pending interrupts
|
||||||
|
I2C_writeReg(I2C_DEV_MCU, 0x22, (u8)(MCU_INT_LCD_BL_ON >> 24));
|
||||||
|
|
||||||
|
// Wait for LCD and backlights to be on
|
||||||
|
do
|
||||||
|
{
|
||||||
|
pend |= mcuGetPendingInterrupts(MCU_INT_LCD_BL_ON);
|
||||||
|
} while (pend != MCU_INT_LCD_BL_ON);
|
||||||
|
|
||||||
|
mcuSetInterruptMask(prevMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcuPowerBacklightsOff(void)
|
||||||
|
{
|
||||||
|
// Doesn't matter if they're already off, it should be idempotent on the MCU side
|
||||||
|
|
||||||
|
u32 prevMask = mcuGetInterruptMask();
|
||||||
|
u32 pend = 0;
|
||||||
|
|
||||||
|
mcuSetInterruptMask(~MCU_INT_LCD_BL_OFF);
|
||||||
|
mcuGetPendingInterrupts(MCU_INT_LCD_BL_OFF) ; // Clear any pending interrupts
|
||||||
|
I2C_writeReg(I2C_DEV_MCU, 0x22, (u8)(MCU_INT_LCD_BL_OFF >> 24));
|
||||||
|
|
||||||
|
// Wait for LCD and backlights to be off
|
||||||
|
do
|
||||||
|
{
|
||||||
|
pend |= mcuGetPendingInterrupts(MCU_INT_LCD_BL_OFF);
|
||||||
|
} while (pend != MCU_INT_LCD_BL_OFF);
|
||||||
|
|
||||||
|
mcuSetInterruptMask(prevMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcuPowerOff(void)
|
||||||
|
{
|
||||||
|
mcuFinalize();
|
||||||
|
I2C_writeReg(I2C_DEV_MCU, 0x20, BIT(0));
|
||||||
|
while(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcuReboot(void)
|
||||||
|
{
|
||||||
|
mcuFinalize();
|
||||||
|
I2C_writeReg(I2C_DEV_MCU, 0x20, BIT(2));
|
||||||
|
while(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcuInit(void)
|
||||||
|
{
|
||||||
|
I2C_init();
|
||||||
|
mcuSetInterruptMask(~MCU_INT_MASK_FOR_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcuFinalize(void)
|
||||||
|
{
|
||||||
|
mcuGetPendingInterrupts(0xFFFFFFFF); // purge pending MCU interrupts & internal pending list
|
||||||
|
mcuSetInterruptMask(~MCU_INT_DEFAULT_MASK); // Reset MCU mask
|
||||||
|
}
|
55
arm9/source/mcu.h
Normal file
55
arm9/source/mcu.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Luma3DS
|
||||||
|
* Copyright (C) 2021 Aurora Wright, TuxSH
|
||||||
|
*
|
||||||
|
* 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 3 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
|
||||||
|
* * Requiring preservation of specified reasonable legal notices or
|
||||||
|
* author attributions in that material or in the Appropriate Legal
|
||||||
|
* Notices displayed by works containing it.
|
||||||
|
* * Prohibiting misrepresentation of the origin of that material,
|
||||||
|
* or requiring that modified versions of such material be marked in
|
||||||
|
* reasonable ways as different from the original version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bit13: Battery low (at 10%, 5% and sub-1%)
|
||||||
|
* Bit7: MCU WDT reset
|
||||||
|
* Bit6: Shell opened (GPIO1_0 1->0)
|
||||||
|
* Bit5: Shell closed (GPIO1_0 0->1)
|
||||||
|
* Bit1: Power button held
|
||||||
|
* Bit0: Power button pressed
|
||||||
|
*/
|
||||||
|
#define MCU_INT_MASK_FOR_INPUT (BIT(13)|BIT(7)|BIT(6)|BIT(5)|BIT(1)|BIT(0))
|
||||||
|
#define MCU_INT_LCD_BL_ON (BIT(29)|BIT(27)|BIT(25))
|
||||||
|
#define MCU_INT_LCD_BL_OFF (BIT(28)|BIT(26)|BIT(24))
|
||||||
|
#define MCU_INT_DEFAULT_MASK 0x0000E7FFu
|
||||||
|
|
||||||
|
u32 mcuGetInterruptMask(void);
|
||||||
|
void mcuSetInterruptMask(u32 mask); // 1 for each interrupt you want to *disable* (mask)
|
||||||
|
u32 mcuGetPendingInterrupts(u32 mask);
|
||||||
|
|
||||||
|
void mcuPowerBacklightsOn(void);
|
||||||
|
void mcuPowerBacklightsOff(void);
|
||||||
|
|
||||||
|
void NORETURN mcuPowerOff(void);
|
||||||
|
void NORETURN mcuReboot(void);
|
||||||
|
|
||||||
|
void mcuInit(void);
|
||||||
|
void mcuFinalize(void);
|
@ -198,7 +198,7 @@ bool verifyPin(u32 pinMode)
|
|||||||
}
|
}
|
||||||
while(!(pressed & PIN_BUTTONS));
|
while(!(pressed & PIN_BUTTONS));
|
||||||
|
|
||||||
if(pressed & BUTTON_START) mcuPowerOff();
|
if(pressed & BUTTON_START) powerOff();
|
||||||
|
|
||||||
pressed &= PIN_BUTTONS;
|
pressed &= PIN_BUTTONS;
|
||||||
|
|
||||||
|
@ -32,8 +32,7 @@
|
|||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "i2c.h"
|
#include "mcu.h"
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
bool needToSetupScreens = true;
|
bool needToSetupScreens = true;
|
||||||
|
|
||||||
@ -69,6 +68,7 @@ void prepareArm11ForFirmlaunch(void)
|
|||||||
|
|
||||||
void deinitScreens(void)
|
void deinitScreens(void)
|
||||||
{
|
{
|
||||||
|
mcuPowerBacklightsOff();
|
||||||
if(ARESCREENSINITIALIZED) invokeArm11Function(DEINIT_SCREENS);
|
if(ARESCREENSINITIALIZED) invokeArm11Function(DEINIT_SCREENS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,19 +102,20 @@ void initScreens(void)
|
|||||||
memcpy((void *)(ARM11_PARAMETERS_ADDRESS + 4), fbs, sizeof(fbs));
|
memcpy((void *)(ARM11_PARAMETERS_ADDRESS + 4), fbs, sizeof(fbs));
|
||||||
invokeArm11Function(INIT_SCREENS);
|
invokeArm11Function(INIT_SCREENS);
|
||||||
|
|
||||||
//Turn on backlight
|
mcuPowerBacklightsOn();
|
||||||
I2C_writeReg(I2C_DEV_MCU, 0x22, 0x2A);
|
|
||||||
wait(5);
|
|
||||||
}
|
}
|
||||||
else updateBrightness(MULTICONFIG(BRIGHTNESS));
|
else updateBrightness(MULTICONFIG(BRIGHTNESS));
|
||||||
|
|
||||||
|
clearScreens(false);
|
||||||
|
clearScreens(true);
|
||||||
|
|
||||||
memcpy((void *)ARM11_PARAMETERS_ADDRESS, fbs, sizeof(fbs));
|
memcpy((void *)ARM11_PARAMETERS_ADDRESS, fbs, sizeof(fbs));
|
||||||
invokeArm11Function(SETUP_FRAMEBUFFERS);
|
invokeArm11Function(SETUP_FRAMEBUFFERS);
|
||||||
|
|
||||||
clearScreens(true);
|
|
||||||
needToSetupScreens = false;
|
needToSetupScreens = false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
clearScreens(false);
|
clearScreens(false);
|
||||||
swapFramebuffers(false);
|
swapFramebuffers(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,14 @@ typedef volatile s16 vs16;
|
|||||||
typedef volatile s32 vs32;
|
typedef volatile s32 vs32;
|
||||||
typedef volatile s64 vs64;
|
typedef volatile s64 vs64;
|
||||||
|
|
||||||
|
/// Creates a bitmask from a bit number.
|
||||||
|
#define BIT(n) (1U<<(n))
|
||||||
|
|
||||||
|
/// Aligns a struct (and other types?) to m, making sure that the size of the struct is a multiple of m.
|
||||||
|
#define ALIGN(m) __attribute__((aligned(m)))
|
||||||
|
|
||||||
|
#define NORETURN __attribute__((noreturn))
|
||||||
|
|
||||||
#include "3dsheaders.h"
|
#include "3dsheaders.h"
|
||||||
|
|
||||||
#define CFG_BOOTENV (*(vu32 *)0x10010000)
|
#define CFG_BOOTENV (*(vu32 *)0x10010000)
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include "fmt.h"
|
#include "fmt.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
#include "mcu.h"
|
||||||
|
|
||||||
static void startChrono(void)
|
static void startChrono(void)
|
||||||
{
|
{
|
||||||
@ -65,13 +66,24 @@ static u64 chrono(void)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void powerOff(void)
|
||||||
|
{
|
||||||
|
if(!needToSetupScreens) clearScreens(false);
|
||||||
|
|
||||||
|
mcuPowerBacklightsOff();
|
||||||
|
|
||||||
|
// Ensure that all memory transfers have completed and that the data cache has been flushed
|
||||||
|
flushEntireDCache();
|
||||||
|
|
||||||
|
mcuPowerOff();
|
||||||
|
}
|
||||||
|
|
||||||
u32 waitInput(bool isMenu)
|
u32 waitInput(bool isMenu)
|
||||||
{
|
{
|
||||||
static u64 dPadDelay = 0ULL;
|
static u64 dPadDelay = 0ULL;
|
||||||
u64 initialValue = 0ULL;
|
u64 initialValue = 0ULL;
|
||||||
u32 key,
|
u32 key,
|
||||||
oldKey = HID_PAD;
|
oldKey = HID_PAD;
|
||||||
bool shouldShellShutdown = bootType != B9SNTR && bootType != NTR;
|
|
||||||
|
|
||||||
if(isMenu)
|
if(isMenu)
|
||||||
{
|
{
|
||||||
@ -82,21 +94,34 @@ u32 waitInput(bool isMenu)
|
|||||||
|
|
||||||
while(true)
|
while(true)
|
||||||
{
|
{
|
||||||
|
// There are two sources of truth for the shell state: the gpio reg and the MCU
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bit13: Battery low (at 10%, 5% and sub-1%)
|
||||||
|
* Bit7: MCU WDT reset
|
||||||
|
* Bit6: Shell opened (GPIO1_0 1->0)
|
||||||
|
* Bit5: Shell closed (GPIO1_0 0->1)
|
||||||
|
* Bit1: Power button held
|
||||||
|
* Bit0: Power button pressed
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ignore "shell opened", just ack
|
||||||
|
u32 mcuInts = mcuGetPendingInterrupts(MCU_INT_MASK_FOR_INPUT);
|
||||||
|
|
||||||
|
if (mcuInts & (BIT(7) | BIT(5) | BIT(1) | BIT(0)))
|
||||||
|
powerOff();
|
||||||
|
|
||||||
|
if (mcuInts & BIT(13))
|
||||||
|
{
|
||||||
|
u8 battLevelIntPart = I2C_readReg(I2C_DEV_MCU, 0xB);
|
||||||
|
if (battLevelIntPart == 0)
|
||||||
|
powerOff();
|
||||||
|
}
|
||||||
|
|
||||||
key = HID_PAD;
|
key = HID_PAD;
|
||||||
|
|
||||||
if(!key)
|
if(!key)
|
||||||
{
|
{
|
||||||
if(shouldShellShutdown)
|
|
||||||
{
|
|
||||||
u8 shellState = I2C_readReg(I2C_DEV_MCU, 0xF);
|
|
||||||
wait(5);
|
|
||||||
if(!(shellState & 2)) mcuPowerOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
u8 intStatus = I2C_readReg(I2C_DEV_MCU, 0x10);
|
|
||||||
wait(5);
|
|
||||||
if(intStatus & 1) mcuPowerOff(); //Power button pressed
|
|
||||||
|
|
||||||
oldKey = 0;
|
oldKey = 0;
|
||||||
dPadDelay = 0;
|
dPadDelay = 0;
|
||||||
continue;
|
continue;
|
||||||
@ -113,20 +138,6 @@ u32 waitInput(bool isMenu)
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mcuPowerOff(void)
|
|
||||||
{
|
|
||||||
if(!needToSetupScreens) clearScreens(false);
|
|
||||||
|
|
||||||
//Shutdown LCD
|
|
||||||
if(ARESCREENSINITIALIZED) I2C_writeReg(I2C_DEV_MCU, 0x22, 1 << 0);
|
|
||||||
|
|
||||||
//Ensure that all memory transfers have completed and that the data cache has been flushed
|
|
||||||
flushEntireDCache();
|
|
||||||
|
|
||||||
I2C_writeReg(I2C_DEV_MCU, 0x20, 1 << 0);
|
|
||||||
while(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wait(u64 amount)
|
void wait(u64 amount)
|
||||||
{
|
{
|
||||||
startChrono();
|
startChrono();
|
||||||
@ -151,6 +162,5 @@ void error(const char *fmt, ...)
|
|||||||
drawString(true, 10, posY + 2 * SPACING_Y, COLOR_WHITE, "Press any button to shutdown");
|
drawString(true, 10, posY + 2 * SPACING_Y, COLOR_WHITE, "Press any button to shutdown");
|
||||||
|
|
||||||
waitInput(false);
|
waitInput(false);
|
||||||
|
powerOff();
|
||||||
mcuPowerOff();
|
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#define MAKE_BRANCH(src,dst) (0xEA000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF))
|
#define MAKE_BRANCH(src,dst) (0xEA000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF))
|
||||||
#define MAKE_BRANCH_LINK(src,dst) (0xEB000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF))
|
#define MAKE_BRANCH_LINK(src,dst) (0xEB000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF))
|
||||||
|
|
||||||
|
void NORETURN powerOff(void);
|
||||||
u32 waitInput(bool isMenu);
|
u32 waitInput(bool isMenu);
|
||||||
void mcuPowerOff(void);
|
|
||||||
void wait(u64 amount);
|
void wait(u64 amount);
|
||||||
void error(const char *fmt, ...);
|
void NORETURN error(const char *fmt, ...);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user