f7d68792b2a55512db173bc66ef1eeca07833c4e
[picodrive.git] / platform / pandora / plat.c
1 /*\r
2  * PicoDrive\r
3  * (C) notaz, 2010,2011\r
4  *\r
5  * This work is licensed under the terms of MAME license.\r
6  * See COPYING file in the top-level directory.\r
7  */\r
8 \r
9 #include <stdio.h>\r
10 #include <unistd.h>\r
11 #include <sys/types.h>\r
12 #include <sys/stat.h>\r
13 #include <fcntl.h>\r
14 #include <sys/ioctl.h>\r
15 #include <unistd.h>\r
16 #include <linux/fb.h>\r
17 #include <linux/omapfb.h>\r
18 #include <linux/input.h>\r
19 \r
20 #include "../common/emu.h"\r
21 #include "../common/arm_utils.h"\r
22 #include "../common/input_pico.h"\r
23 #include "../common/version.h"\r
24 #include "../libpicofe/input.h"\r
25 #include "../libpicofe/menu.h"\r
26 #include "../libpicofe/plat.h"\r
27 #include "../libpicofe/linux/in_evdev.h"\r
28 #include "../libpicofe/linux/sndout_oss.h"\r
29 #include "../libpicofe/linux/fbdev.h"\r
30 #include "../libpicofe/linux/xenv.h"\r
31 #include "plat.h"\r
32 #include "asm_utils.h"\r
33 \r
34 #include <pico/pico_int.h>\r
35 \r
36 #define LAYER_MEM_SIZE (320*240*2 * 4)\r
37 \r
38 static struct vout_fbdev *main_fb, *layer_fb;\r
39 // g_layer_* - in use, g_layer_c* - configured custom\r
40 int g_layer_cx, g_layer_cy, g_layer_cw, g_layer_ch;\r
41 static int g_layer_x, g_layer_y;\r
42 static int g_layer_w = 320, g_layer_h = 240;\r
43 static int g_osd_fps_x, g_osd_y, doing_bg_frame;\r
44 \r
45 static unsigned char __attribute__((aligned(4))) fb_copy[320 * 240 * 2];\r
46 static void *temp_frame;\r
47 const char *renderer_names[] = { NULL };\r
48 const char *renderer_names32x[] = { NULL };\r
49 \r
50 static const char * const pandora_gpio_keys[KEY_MAX + 1] = {\r
51         [0 ... KEY_MAX] = NULL,\r
52         [KEY_UP]        = "Up",\r
53         [KEY_LEFT]      = "Left",\r
54         [KEY_RIGHT]     = "Right",\r
55         [KEY_DOWN]      = "Down",\r
56         [KEY_HOME]      = "A",\r
57         [KEY_PAGEDOWN]  = "X",\r
58         [KEY_END]       = "B",\r
59         [KEY_PAGEUP]    = "Y",\r
60         [KEY_RIGHTSHIFT]= "L",\r
61         [KEY_RIGHTCTRL] = "R",\r
62         [KEY_LEFTALT]   = "Start",\r
63         [KEY_LEFTCTRL]  = "Select",\r
64         [KEY_MENU]      = "Pandora",\r
65 };\r
66 \r
67 static struct in_default_bind in_evdev_defbinds[] =\r
68 {\r
69         { KEY_UP,       IN_BINDTYPE_PLAYER12, GBTN_UP },\r
70         { KEY_DOWN,     IN_BINDTYPE_PLAYER12, GBTN_DOWN },\r
71         { KEY_LEFT,     IN_BINDTYPE_PLAYER12, GBTN_LEFT },\r
72         { KEY_RIGHT,    IN_BINDTYPE_PLAYER12, GBTN_RIGHT },\r
73         { KEY_A,        IN_BINDTYPE_PLAYER12, GBTN_A },\r
74         { KEY_S,        IN_BINDTYPE_PLAYER12, GBTN_B },\r
75         { KEY_D,        IN_BINDTYPE_PLAYER12, GBTN_C },\r
76         { KEY_ENTER,    IN_BINDTYPE_PLAYER12, GBTN_START },\r
77         { KEY_F,        IN_BINDTYPE_EMU, PEVB_FF },\r
78         { KEY_BACKSPACE,IN_BINDTYPE_EMU, PEVB_FF },\r
79         { KEY_BACKSLASH,IN_BINDTYPE_EMU, PEVB_MENU },\r
80         { KEY_SPACE,    IN_BINDTYPE_EMU, PEVB_MENU },\r
81         { KEY_LEFTCTRL, IN_BINDTYPE_EMU, PEVB_MENU },\r
82         { KEY_HOME,     IN_BINDTYPE_PLAYER12, GBTN_A },\r
83         { KEY_PAGEDOWN, IN_BINDTYPE_PLAYER12, GBTN_B },\r
84         { KEY_END,      IN_BINDTYPE_PLAYER12, GBTN_C },\r
85         { KEY_LEFTALT,  IN_BINDTYPE_PLAYER12, GBTN_START },\r
86         { KEY_1,        IN_BINDTYPE_EMU, PEVB_STATE_SAVE },\r
87         { KEY_2,        IN_BINDTYPE_EMU, PEVB_STATE_LOAD },\r
88         { KEY_3,        IN_BINDTYPE_EMU, PEVB_SSLOT_PREV },\r
89         { KEY_4,        IN_BINDTYPE_EMU, PEVB_SSLOT_NEXT },\r
90         { KEY_5,        IN_BINDTYPE_EMU, PEVB_PICO_PPREV },\r
91         { KEY_6,        IN_BINDTYPE_EMU, PEVB_PICO_PNEXT },\r
92         { KEY_7,        IN_BINDTYPE_EMU, PEVB_PICO_SWINP },\r
93         { 0, 0, 0 }\r
94 };\r
95 \r
96 void pemu_prep_defconfig(void)\r
97 {\r
98         defaultConfig.EmuOpt |= EOPT_VSYNC|EOPT_16BPP;\r
99         defaultConfig.s_PicoOpt |= POPT_EN_MCD_GFX;\r
100         defaultConfig.scaling = SCALE_2x2_3x2;\r
101 }\r
102 \r
103 void pemu_validate_config(void)\r
104 {\r
105         currentConfig.CPUclock = plat_target_cpu_clock_get();\r
106 }\r
107 \r
108 static void osd_text(int x, int y, const char *text)\r
109 {\r
110         int len = strlen(text)*8;\r
111         int i, h;\r
112 \r
113         len++;\r
114         if (x + len > g_screen_width)\r
115                 len = g_screen_width - x;\r
116 \r
117         for (h = 0; h < 8; h++) {\r
118                 unsigned short *p;\r
119                 p = (unsigned short *)g_screen_ptr + x + g_screen_width*(y + h);\r
120                 for (i = len; i > 0; i--, p++)\r
121                         *p = (*p>>2) & 0x39e7;\r
122         }\r
123         emu_text_out16(x, y, text);\r
124 }\r
125 \r
126 static void draw_cd_leds(void)\r
127 {\r
128         int old_reg;\r
129         old_reg = Pico_mcd->s68k_regs[0];\r
130 \r
131         if (0) {\r
132                 // 8-bit modes\r
133                 unsigned int col_g = (old_reg & 2) ? 0xc0c0c0c0 : 0xe0e0e0e0;\r
134                 unsigned int col_r = (old_reg & 1) ? 0xd0d0d0d0 : 0xe0e0e0e0;\r
135                 *(unsigned int *)((char *)g_screen_ptr + g_screen_width*2+ 4) =\r
136                 *(unsigned int *)((char *)g_screen_ptr + g_screen_width*3+ 4) =\r
137                 *(unsigned int *)((char *)g_screen_ptr + g_screen_width*4+ 4) = col_g;\r
138                 *(unsigned int *)((char *)g_screen_ptr + g_screen_width*2+12) =\r
139                 *(unsigned int *)((char *)g_screen_ptr + g_screen_width*3+12) =\r
140                 *(unsigned int *)((char *)g_screen_ptr + g_screen_width*4+12) = col_r;\r
141         } else {\r
142                 // 16-bit modes\r
143                 unsigned int *p = (unsigned int *)((short *)g_screen_ptr + g_screen_width*2+4);\r
144                 unsigned int col_g = (old_reg & 2) ? 0x06000600 : 0;\r
145                 unsigned int col_r = (old_reg & 1) ? 0xc000c000 : 0;\r
146                 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += g_screen_width/2 - 12/2;\r
147                 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += g_screen_width/2 - 12/2;\r
148                 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r;\r
149         }\r
150 }\r
151 \r
152 void pemu_finalize_frame(const char *fps, const char *notice)\r
153 {\r
154         if (notice && notice[0])\r
155                 osd_text(2, g_osd_y, notice);\r
156         if (fps && fps[0] && (currentConfig.EmuOpt & EOPT_SHOW_FPS))\r
157                 osd_text(g_osd_fps_x, g_osd_y, fps);\r
158         if ((PicoAHW & PAHW_MCD) && (currentConfig.EmuOpt & EOPT_EN_CD_LEDS))\r
159                 draw_cd_leds();\r
160 }\r
161 \r
162 void plat_video_flip(void)\r
163 {\r
164         g_screen_ptr = vout_fbdev_flip(layer_fb);\r
165         PicoDrawSetOutBuf(g_screen_ptr, g_screen_width * 2);\r
166 \r
167         // XXX: drain OS event queue here, maybe we'll actually use it someday..\r
168         xenv_update(NULL, NULL, NULL, NULL);\r
169 }\r
170 \r
171 void plat_video_toggle_renderer(int change, int is_menu)\r
172 {\r
173 }\r
174 \r
175 void plat_video_menu_enter(int is_rom_loaded)\r
176 {\r
177 }\r
178 \r
179 void plat_video_menu_begin(void)\r
180 {\r
181 }\r
182 \r
183 void plat_video_menu_end(void)\r
184 {\r
185         g_menuscreen_ptr = vout_fbdev_flip(main_fb);\r
186 }\r
187 \r
188 void plat_video_menu_leave(void)\r
189 {\r
190 }\r
191 \r
192 void plat_video_wait_vsync(void)\r
193 {\r
194         vout_fbdev_wait_vsync(main_fb);\r
195 }\r
196 \r
197 void plat_status_msg_clear(void)\r
198 {\r
199         vout_fbdev_clear_lines(layer_fb, g_osd_y, 8);\r
200 }\r
201 \r
202 void plat_status_msg_busy_next(const char *msg)\r
203 {\r
204         plat_status_msg_clear();\r
205         pemu_finalize_frame("", msg);\r
206         plat_video_flip();\r
207         emu_status_msg("");\r
208         reset_timing = 1;\r
209 }\r
210 \r
211 void plat_status_msg_busy_first(const char *msg)\r
212 {\r
213         plat_status_msg_busy_next(msg);\r
214 }\r
215 \r
216 void plat_update_volume(int has_changed, int is_up)\r
217 {\r
218 }\r
219 \r
220 void pemu_forced_frame(int no_scale, int do_emu)\r
221 {\r
222         doing_bg_frame = 1;\r
223         emu_cmn_forced_frame(no_scale, do_emu);\r
224         doing_bg_frame = 0;\r
225 \r
226         // making a copy because enabling the layer clears it's mem\r
227         memcpy((void *)fb_copy, g_screen_ptr, sizeof(fb_copy));\r
228         g_menubg_src_ptr = fb_copy;\r
229 }\r
230 \r
231 void pemu_sound_start(void)\r
232 {\r
233         emu_sound_start();\r
234 }\r
235 \r
236 void plat_debug_cat(char *str)\r
237 {\r
238 }\r
239 \r
240 static int pnd_setup_layer_(int fd, int enabled, int x, int y, int w, int h)\r
241 {\r
242         struct omapfb_plane_info pi;\r
243         struct omapfb_mem_info mi;\r
244         int is_enabled;\r
245         int retval = 0;\r
246         int ret;\r
247 \r
248         ret = ioctl(fd, OMAPFB_QUERY_PLANE, &pi);\r
249         if (ret != 0) {\r
250                 perror("QUERY_PLANE");\r
251                 return -1;\r
252         }\r
253 \r
254         ret = ioctl(fd, OMAPFB_QUERY_MEM, &mi);\r
255         if (ret != 0) {\r
256                 perror("QUERY_MEM");\r
257                 return -1;\r
258         }\r
259 \r
260         /* must disable when changing stuff */\r
261         is_enabled = pi.enabled;\r
262         if (is_enabled) {\r
263                 pi.enabled = 0;\r
264                 ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi);\r
265                 if (ret != 0)\r
266                         perror("SETUP_PLANE");\r
267                 else\r
268                         is_enabled = 0;\r
269         }\r
270 \r
271         if (mi.size < LAYER_MEM_SIZE) {\r
272                 unsigned int size_old = mi.size;\r
273 \r
274                 mi.size = LAYER_MEM_SIZE;\r
275                 ret = ioctl(fd, OMAPFB_SETUP_MEM, &mi);\r
276                 if (ret != 0) {\r
277                         perror("SETUP_MEM");\r
278                         fprintf(stderr, "(requested %u, had %u)\n",\r
279                                 mi.size, size_old);\r
280                         return -1;\r
281                 }\r
282         }\r
283 \r
284         pi.pos_x = x;\r
285         pi.pos_y = y;\r
286         pi.out_width = w;\r
287         pi.out_height = h;\r
288         pi.enabled = enabled;\r
289 \r
290         ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi);\r
291         if (ret == 0) {\r
292                 is_enabled = pi.enabled;\r
293         }\r
294         else {\r
295                 perror("SETUP_PLANE");\r
296                 retval = -1;\r
297         }\r
298 \r
299         plat_target_switch_layer(1, is_enabled);\r
300 \r
301         return retval;\r
302 }\r
303 \r
304 int pnd_setup_layer(int enabled, int x, int y, int w, int h)\r
305 {\r
306         return pnd_setup_layer_(vout_fbdev_get_fd(layer_fb), enabled, x, y, w, h);\r
307 }\r
308 \r
309 void pnd_restore_layer_data(void)\r
310 {\r
311         short *t = (short *)fb_copy + 320*240 / 2 + 160;\r
312 \r
313         // right now this is used by menu, which wants to preview something\r
314         // so try to get something on the layer.\r
315         if ((t[0] | t[5] | t[13]) == 0)\r
316                 memset32((void *)fb_copy, 0x07000700, sizeof(fb_copy) / 4);\r
317 \r
318         memcpy(g_screen_ptr, (void *)fb_copy, 320*240*2);\r
319         plat_video_flip();\r
320 }\r
321 \r
322 void emu_video_mode_change(int start_line, int line_count, int is_32cols)\r
323 {\r
324         int fb_w = 320, fb_h = 240, fb_left = 0, fb_right = 0, fb_top = 0, fb_bottom = 0;\r
325 \r
326         if (doing_bg_frame)\r
327                 return;\r
328 \r
329         if (is_32cols) {\r
330                 fb_w = 256;\r
331                 fb_left = fb_right = 32;\r
332         }\r
333 \r
334         switch (currentConfig.scaling) {\r
335         case SCALE_1x1:\r
336                 g_layer_w = fb_w;\r
337                 g_layer_h = fb_h;\r
338                 break;\r
339         case SCALE_2x2_3x2:\r
340                 g_layer_w = fb_w * (is_32cols ? 3 : 2);\r
341                 g_layer_h = fb_h * 2;\r
342                 break;\r
343         case SCALE_2x2_2x2:\r
344                 g_layer_w = fb_w * 2;\r
345                 g_layer_h = fb_h * 2;\r
346                 break;\r
347         case SCALE_FULLSCREEN:\r
348                 g_layer_w = 800;\r
349                 g_layer_h = 480;\r
350                 break;\r
351         case SCALE_CUSTOM:\r
352                 g_layer_x = g_layer_cx;\r
353                 g_layer_y = g_layer_cy;\r
354                 g_layer_w = g_layer_cw;\r
355                 g_layer_h = g_layer_ch;\r
356                 break;\r
357         }\r
358 \r
359         if (currentConfig.scaling != SCALE_CUSTOM) {\r
360                 // center the layer\r
361                 g_layer_x = 800 / 2 - g_layer_w / 2;\r
362                 g_layer_y = 480 / 2 - g_layer_h / 2;\r
363         }\r
364 \r
365         switch (currentConfig.scaling) {\r
366         case SCALE_FULLSCREEN:\r
367         case SCALE_CUSTOM:\r
368                 fb_top = start_line;\r
369                 fb_h = line_count;\r
370                 break;\r
371         }\r
372         g_osd_fps_x = is_32cols ? 232 : 264;\r
373         g_osd_y = fb_top + fb_h - 8;\r
374 \r
375         pnd_setup_layer(1, g_layer_x, g_layer_y, g_layer_w, g_layer_h);\r
376         vout_fbdev_clear(layer_fb);\r
377         vout_fbdev_resize(layer_fb, fb_w, fb_h, 16, fb_left, fb_right, fb_top, fb_bottom, 4);\r
378         plat_video_flip();\r
379 \r
380         PicoDrawSetOutFormat(PDF_RGB555, 0);\r
381 }\r
382 \r
383 void plat_video_loop_prepare(void)\r
384 {\r
385         // make sure there is no junk left behind the layer\r
386         memset(g_menuscreen_ptr, 0, g_menuscreen_w * g_menuscreen_h * 2);\r
387         g_menuscreen_ptr = vout_fbdev_flip(main_fb);\r
388 \r
389         // emu_video_mode_change will call pnd_setup_layer()\r
390 }\r
391 \r
392 void pemu_loop_prep(void)\r
393 {\r
394         // dirty buffers better go now than during gameplay\r
395         fflush(stdout);\r
396         fflush(stderr);\r
397         sync();\r
398         sleep(0);\r
399 }\r
400 \r
401 void pemu_loop_end(void)\r
402 {\r
403         /* do one more frame for menu bg */\r
404         pemu_forced_frame(0, 1);\r
405 \r
406         pnd_setup_layer(0, g_layer_x, g_layer_y, g_layer_w, g_layer_h);\r
407 }\r
408 \r
409 void plat_wait_till_us(unsigned int us_to)\r
410 {\r
411         unsigned int now;\r
412         signed int diff;\r
413 \r
414         now = plat_get_ticks_us();\r
415 \r
416         // XXX: need to check NOHZ\r
417         diff = (signed int)(us_to - now);\r
418         if (diff > 10000) {\r
419                 //printf("sleep %d\n", us_to - now);\r
420                 usleep(diff * 15 / 16);\r
421                 now = plat_get_ticks_us();\r
422                 //printf(" wake %d\n", (signed)(us_to - now));\r
423         }\r
424 /*\r
425         while ((signed int)(us_to - now) > 512) {\r
426                 spend_cycles(1024);\r
427                 now = plat_get_ticks_us();\r
428         }\r
429 */\r
430 }\r
431 \r
432 void plat_early_init(void)\r
433 {\r
434 }\r
435 \r
436 void plat_init(void)\r
437 {\r
438         const char *main_fb_name, *layer_fb_name;\r
439         int fd, ret, w, h;\r
440 \r
441         main_fb_name = getenv("FBDEV_MAIN");\r
442         if (main_fb_name == NULL)\r
443                 main_fb_name = "/dev/fb0";\r
444 \r
445         layer_fb_name = getenv("FBDEV_LAYER");\r
446         if (layer_fb_name == NULL)\r
447                 layer_fb_name = "/dev/fb1";\r
448 \r
449         // must set the layer up first to be able to use it\r
450         fd = open(layer_fb_name, O_RDWR);\r
451         if (fd == -1) {\r
452                 fprintf(stderr, "%s: ", layer_fb_name);\r
453                 perror("open");\r
454                 exit(1);\r
455         }\r
456 \r
457         ret = pnd_setup_layer_(fd, 0, g_layer_x, g_layer_y, g_layer_w, g_layer_h);\r
458         close(fd);\r
459         if (ret != 0) {\r
460                 fprintf(stderr, "failed to set up layer, exiting.\n");\r
461                 exit(1);\r
462         }\r
463 \r
464         xenv_init(NULL, "PicoDrive " VERSION);\r
465 \r
466         w = h = 0;\r
467         main_fb = vout_fbdev_init(main_fb_name, &w, &h, 16, 2);\r
468         if (main_fb == NULL) {\r
469                 fprintf(stderr, "couldn't init fb: %s\n", main_fb_name);\r
470                 exit(1);\r
471         }\r
472 \r
473         g_menuscreen_w = w;\r
474         g_menuscreen_h = h;\r
475         g_menuscreen_ptr = vout_fbdev_flip(main_fb);\r
476 \r
477         w = 320; h = 240;\r
478         layer_fb = vout_fbdev_init(layer_fb_name, &w, &h, 16, 4);\r
479         if (layer_fb == NULL) {\r
480                 fprintf(stderr, "couldn't init fb: %s\n", layer_fb_name);\r
481                 goto fail0;\r
482         }\r
483 \r
484         if (w != g_screen_width || h != g_screen_height) {\r
485                 fprintf(stderr, "%dx%d not supported on %s\n", w, h, layer_fb_name);\r
486                 goto fail1;\r
487         }\r
488         g_screen_ptr = vout_fbdev_flip(layer_fb);\r
489 \r
490         temp_frame = calloc(g_menuscreen_w * g_menuscreen_h * 2, 1);\r
491         if (temp_frame == NULL) {\r
492                 fprintf(stderr, "OOM\n");\r
493                 goto fail1;\r
494         }\r
495         g_menubg_ptr = temp_frame;\r
496         g_menubg_src_ptr = temp_frame;\r
497 \r
498         pnd_menu_init();\r
499 \r
500         // default ROM path\r
501         strcpy(rom_fname_loaded, "/media");\r
502 \r
503         in_evdev_init(in_evdev_defbinds);\r
504         in_probe();\r
505         plat_target_setup_input();\r
506 \r
507         sndout_oss_frag_frames = 2;\r
508 \r
509         return;\r
510 \r
511 fail1:\r
512         vout_fbdev_finish(layer_fb);\r
513 fail0:\r
514         vout_fbdev_finish(main_fb);\r
515         exit(1);\r
516 }\r
517 \r
518 void plat_finish(void)\r
519 {\r
520         vout_fbdev_finish(main_fb);\r
521         xenv_finish();\r
522 \r
523         printf("all done\n");\r
524 }\r
525 \r