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