Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

echoplayer: implement bootloader power on/off logic

On the Echo R1, the main regulator is enabled primarily by
the power button and USB input, and secondarily by the CPU's
own output pins (cpu_power_on signal or RTC alarm output).

From a user perspective, the player should appear to power
up and down only if the power button is long pressed, which
must be implemented in software. These logical power states
are called "active" and "inactive" in the bootloader.

Going from inactive to active will attempt to boot Rockbox
unless a button (d-pad down) is held to enter bootloader
USB mode instead. Going from active to inactive will shut
down the player. The bootloader will also automatically
shut down after a short timeout, if USB is not plugged in.

In the inactive state, the player is supposed to enumerate
over USB so it can negotiate a charging current, but should
otherwise appear "off". In particular it shouldn't expose
mass storage or even power up the SD card, nor power up the
LCD/backight. This isn't implemented yet, because there's
no way to dynamically change USB configurations (eg. going
from active to inactive should trigger re-enumeration to
switch to charge only mode). To avoid surprising behavior,
the bootloader will just boot Rockbox immediately if USB is
plugged in at boot.

Change-Id: Icd1d48ef49a31eb32b54d440e9211aaf40c6b974

authored by

Aidan MacDonald and committed by
Solomon Peachy
0f5c4212 58ace97a

+242 -84
-1
bootloader/SOURCES
··· 96 96 x1000/utils.c 97 97 #elif defined(ECHO_R1) 98 98 echoplayer.c 99 - show_logo.c 100 99 #endif
+242 -83
bootloader/echoplayer.c
··· 21 21 #include "kernel/kernel-internal.h" 22 22 #include "system.h" 23 23 #include "power.h" 24 - #include "rtc.h" 25 24 #include "lcd.h" 26 25 #include "backlight.h" 27 26 #include "button.h" 28 - #include "timefuncs.h" 29 27 #include "storage.h" 30 28 #include "disk.h" 31 29 #include "file_internal.h" 32 30 #include "usb.h" 33 - #include "common.h" /* For show_logo() */ 31 + #include "rbversion.h" 32 + #include "system-echoplayer.h" 33 + #include "gpio-stm32h7.h" 34 + 35 + /* Events for the monitor callback to signal the main thread */ 36 + #define EV_POWER_PRESSED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0) 37 + #define EV_POWER_RELEASED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1) 38 + #define EV_USB_UNPLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 2) 39 + 40 + /* Long press duration for power button */ 41 + #define POWERBUTTON_LONG_PRESS_TIME (HZ) 34 42 35 - static bool is_usb_connected = false; 43 + /* Time to remain powered with no USB cable inserted */ 44 + #define USB_UNPLUGGED_ACTIVE_TIME (30 * HZ) 45 + #define USB_UNPLUGGED_INACTIVE_TIME (3 * HZ) 36 46 37 - static void demo_rtc(void) 38 - { 39 - int y = 0; 40 - struct tm *time = get_time(); 47 + /* Power button monitor state */ 48 + static bool pwr_curr_state; 49 + static bool pwr_prev_state; 50 + struct timeout pwr_stable_tmo; 41 51 42 - lcd_clear_display(); 52 + /* USB monitor state */ 53 + static bool usb_curr_state; 54 + static bool usb_prev_state; 55 + struct timeout usb_unplugged_tmo; 43 56 44 - lcd_putsf(0, y++, "time: %02d:%02d:%02d", 45 - time->tm_hour, time->tm_min, time->tm_sec); 46 - lcd_putsf(0, y++, "year: %d", time->tm_year + 1900); 47 - lcd_putsf(0, y++, "month: %d", time->tm_mon); 48 - lcd_putsf(0, y++, "day: %d", time->tm_mday); 57 + static volatile bool restart_pwr_stable_tmo; 58 + static volatile bool restart_usb_unplugged_tmo; 49 59 50 - lcd_update(); 60 + /* 61 + * Because power is always enabled while USB is plugged in the 62 + * bootloader decides whether to appear "active" or "inactive" 63 + * to the user. 64 + */ 65 + static bool is_active; 66 + 67 + /* 68 + * This flag is set if the bootloader is entered after software 69 + * poweroff. The user may still be holding the power button and 70 + * we don't want to boot Rockbox because of this, so we have to 71 + * wait for the power button to be released first. 72 + */ 73 + static bool wait_for_power_released; 74 + 75 + /* Optional error message displayed on LCD */ 76 + static const char *status_msg = NULL; 77 + 78 + /* Helper functions */ 79 + static bool is_power_button_pressed(void) 80 + { 81 + return button_status() & BUTTON_POWER; 51 82 } 52 83 53 - static void demo_storage(void) 84 + static bool is_usbmode_button_pressed(void) 54 85 { 55 - int y = 0; 86 + return button_status() & BUTTON_DOWN; 87 + } 88 + 89 + static int send_event_on_tmo(struct timeout *tmo) 90 + { 91 + button_queue_post(tmo->data, 0); 92 + return 0; 93 + } 56 94 57 - lcd_clear_display(); 58 - lcd_putsf(0, y++, "tick %ld", current_tick); 95 + /* 96 + * Monitors the state of the power button and USB cable 97 + * insertion status. It will post an event to the main 98 + * thread when (a) the power button is continously held 99 + * or released for long enough, or (b) the USB cable is 100 + * unplugged for long enough. 101 + */ 102 + static void monitor_tick(void) 103 + { 104 + /* Power button state */ 105 + pwr_prev_state = pwr_curr_state; 106 + pwr_curr_state = is_power_button_pressed(); 59 107 60 - if (is_usb_connected) 108 + if (pwr_curr_state != pwr_prev_state || restart_pwr_stable_tmo) 61 109 { 62 - lcd_puts(0, y++, "storage disabled by USB"); 63 - lcd_update(); 64 - return; 110 + long event = pwr_curr_state ? EV_POWER_PRESSED : EV_POWER_RELEASED; 111 + int ticks = POWERBUTTON_LONG_PRESS_TIME; 112 + 113 + restart_pwr_stable_tmo = false; 114 + timeout_register(&pwr_stable_tmo, send_event_on_tmo, ticks, event); 65 115 } 66 116 67 - struct partinfo pinfo; 68 - if (storage_present(IF_MD(0,)) && disk_partinfo(0, &pinfo)) 117 + /* USB cable state */ 118 + usb_prev_state = usb_curr_state; 119 + usb_curr_state = usb_inserted(); 120 + 121 + /* Ignore cable state change in inactive state */ 122 + if (usb_curr_state != usb_prev_state || restart_usb_unplugged_tmo) 69 123 { 70 - lcd_putsf(0, y++, "start %d", (int)pinfo.start); 71 - lcd_putsf(0, y++, "count %d", (int)pinfo.size); 72 - lcd_putsf(0, y++, "type %d", (int)pinfo.type); 124 + long event = EV_USB_UNPLUGGED; 125 + int ticks = is_active ? USB_UNPLUGGED_ACTIVE_TIME : USB_UNPLUGGED_INACTIVE_TIME; 73 126 74 - DIR *d = opendir("/"); 75 - struct dirent *ent; 76 - while ((ent = readdir(d))) 77 - { 78 - lcd_putsf(0, y++, "/%s", ent->d_name); 79 - } 127 + restart_usb_unplugged_tmo = false; 80 128 81 - closedir(d); 129 + if (usb_curr_state) 130 + timeout_cancel(&usb_unplugged_tmo); 131 + else 132 + timeout_register(&usb_unplugged_tmo, send_event_on_tmo, ticks, event); 82 133 } 134 + } 83 135 84 - lcd_update(); 136 + static void monitor_init(void) 137 + { 138 + pwr_curr_state = is_power_button_pressed(); 139 + usb_curr_state = usb_inserted(); 140 + 141 + /* Make sure events fire even if inputs don't change after boot */ 142 + restart_pwr_stable_tmo = true; 143 + restart_usb_unplugged_tmo = true; 144 + 145 + tick_add_task(monitor_tick); 85 146 } 86 147 87 - static void demo_usb(void) 148 + static void go_active(void) 88 149 { 89 - static const char *phyname[] = { 90 - [STM32H743_USBOTG_PHY_ULPI_HS] = "ULPI HS", 91 - [STM32H743_USBOTG_PHY_ULPI_FS] = "ULPI FS", 92 - [STM32H743_USBOTG_PHY_INT_FS] = "internal FS", 93 - }; 150 + is_active = true; 151 + restart_usb_unplugged_tmo = true; 152 + 153 + gpio_set_level(GPIO_CPU_POWER_ON, 1); 154 + storage_enable(true); 155 + } 156 + 157 + static void go_inactive(void) 158 + { 159 + is_active = false; 160 + restart_usb_unplugged_tmo = true; 161 + 162 + storage_enable(false); 163 + gpio_set_level(GPIO_CPU_POWER_ON, 0); 164 + } 165 + 166 + static void refresh_display(void) 167 + { 168 + if (!is_active) 169 + { 170 + lcd_shutdown(); 171 + return; 172 + } 94 173 95 174 int y = 0; 96 175 97 176 lcd_clear_display(); 98 - lcd_putsf(0, y++, "tick %ld", current_tick); 99 - lcd_putsf(0, y++, "usb connected %d", (int)is_usb_connected); 177 + 178 + lcd_putsf(0, y++, "Rockbox on %s", MODEL_NAME); 179 + y++; 180 + 181 + if (status_msg) 182 + { 183 + lcd_putsf(0, y++, "Error: %s", status_msg); 184 + y++; 185 + } 186 + 187 + if (!usb_inserted()) 188 + { 189 + lcd_putsf(0, y++, "Hold POWER to power off"); 190 + lcd_putsf(0, y++, "Connect USB cable for USB mode"); 191 + y++; 192 + } 193 + else 194 + { 195 + lcd_putsf(0, y++, "Bootloader USB mode"); 196 + 197 + if (charging_state()) 198 + { 199 + lcd_putsf(0, y++, "Battery charging (%d mA)", 200 + usb_charging_maxcurrent()); 201 + } 202 + else 203 + { 204 + lcd_putsf(0, y++, "Battery charged"); 205 + } 100 206 101 - lcd_putsf(0, y++, "instance = USB%d", STM32H743_USBOTG_INSTANCE + 1); 102 - lcd_putsf(0, y++, "phy = %s", phyname[STM32H743_USBOTG_PHY]); 207 + y++; 208 + } 209 + 210 + lcd_putsf(0, y++, "Version: %s", RBVERSION); 103 211 104 212 lcd_update(); 213 + lcd_enable(true); 105 214 } 106 215 107 - static void (*demo_funcs[]) (void) = { 108 - demo_rtc, 109 - demo_storage, 110 - demo_usb, 111 - }; 216 + static void launch(void) 217 + { 218 + /* No-op if USB mode was requested */ 219 + if (is_usbmode_button_pressed()) 220 + return; 221 + 222 + /* TODO: load rockbox */ 223 + status_msg = "Can't boot RB yet!"; 224 + } 112 225 113 226 void main(void) 114 227 { 115 228 system_init(); 116 229 kernel_init(); 117 230 power_init(); 118 - rtc_init(); 119 - 120 - lcd_init(); 121 231 button_init(); 122 232 123 - backlight_init(); 124 - backlight_on(); 233 + /* Start monitoring power button / usb state */ 234 + monitor_init(); 125 235 126 - show_logo(); 236 + /* Prepare LCD in case we need to display something */ 237 + lcd_init(); 238 + backlight_init(); 127 239 240 + /* 241 + * Prepare storage subsystem, but keep SD card unpowered 242 + * until we actually need to access it. 243 + */ 128 244 storage_init(); 245 + storage_enable(false); 246 + 247 + /* 248 + * Initialize fs/disk internal state, the disk will not 249 + * be mountable due to being disabled but this will not 250 + * cause any fatal errors. 251 + */ 129 252 filesystem_init(); 130 253 disk_mount_all(); 131 254 255 + if (echoplayer_boot_reason == ECHOPLAYER_BOOT_REASON_SW_REBOOT || 256 + usb_detect() == USB_INSERTED) 257 + { 258 + /* 259 + * For software reboot we want to immediately launch Rockbox. 260 + * 261 + * We also do so if USB is plugged in at boot, because there's 262 + * no way to dynamically switch between charge only and mass 263 + * storage mode depending on the active/inactive state; to avoid 264 + * confusion, it is simpler to just boot Rockbox. 265 + */ 266 + go_active(); 267 + launch(); 268 + } 269 + else if (echoplayer_boot_reason == ECHOPLAYER_BOOT_REASON_SW_POWEROFF) 270 + { 271 + /* Ignore power button pressed event until button is first released */ 272 + wait_for_power_released = true; 273 + } 274 + 275 + /* 276 + * Initialize USB so we can enumerate with the host and 277 + * negotiate charge current (needed even in inactive mode) 278 + */ 132 279 usb_init(); 133 280 usb_start_monitoring(); 281 + usb_charging_enable(USB_CHARGING_FORCE); 134 282 135 - int demo_page = 0; 136 - const int num_pages = ARRAYLEN(demo_funcs); 283 + for (;;) 284 + { 285 + refresh_display(); 137 286 138 - while (1) 139 - { 140 - int btn = button_get_w_tmo(HZ); 141 - switch (btn) 287 + long refresh_tmo = is_active ? HZ : TIMEOUT_BLOCK; 288 + 289 + switch (button_get_w_tmo(refresh_tmo)) 142 290 { 143 - case BUTTON_START: 144 - demo_page += 1; 145 - if (demo_page >= num_pages) 146 - demo_page = 0; 147 - break; 291 + case EV_POWER_PRESSED: 292 + if (wait_for_power_released) 293 + break; 148 294 149 - case BUTTON_SELECT: 150 - if (demo_page == 0) 151 - demo_page = num_pages - 1; 295 + if (!is_active) 296 + { 297 + /* Launch Rockbox due to user pressing power button */ 298 + go_active(); 299 + launch(); 300 + } 152 301 else 153 - demo_page -= 1; 154 - break; 302 + { 303 + /* 304 + * NOTE: The check for USB insertion here is only 305 + * because we can't change to charging only mode. 306 + */ 307 + if (!usb_inserted()) 308 + go_inactive(); 309 + } 155 310 156 - case SYS_USB_CONNECTED: 157 - usb_acknowledge(SYS_USB_CONNECTED_ACK); 158 - is_usb_connected = true; 159 311 break; 160 312 161 - case SYS_USB_DISCONNECTED: 162 - is_usb_connected = false; 313 + case EV_POWER_RELEASED: 314 + /* 315 + * This cuts power if the power button is not 316 + * held and we're not in the active state due 317 + * to USB mode, etc. 318 + */ 319 + wait_for_power_released = false; 320 + gpio_set_level(GPIO_CPU_POWER_ON, is_active); 321 + break; 163 322 164 - case BUTTON_X: 165 - power_off(); 323 + case EV_USB_UNPLUGGED: 324 + go_inactive(); 166 325 break; 167 326 168 - default: 327 + case SYS_USB_CONNECTED: 328 + go_active(); 329 + usb_acknowledge(SYS_USB_CONNECTED_ACK); 169 330 break; 170 331 } 171 - 172 - demo_funcs[demo_page](); 173 332 } 174 333 }