/* * Video driver for PXA 27x Overlay 2, in conjunction with kernel driver * by Tim Chick * (C) 2007 */ #include #include #include #include #include "config.h" #include "video_out.h" #include "video_out_internal.h" #include "sub.h" #include "aspect.h" #include "mp_msg.h" #include "subopt-helper.h" #include "vo_pxa.h" static vo_info_t info = { "PXA 27x Framebuffer", "pxa", "Tim Chick ", "For Sharp Zaurus SL-C1000 etc" }; LIBVO_EXTERN(pxa); static pxa_priv_t st_pxa_priv; /***************************************************************************** * preinit * * Preinitializes driver * arg - currently it's vo_subdevice * returns: zero on successful initialization, non-zero on error. * ****************************************************************************/ static int preinit(const char *vo_subdevice) { pxa_priv_t *priv = &st_pxa_priv; int rc; mp_msg(MSGT_VO, MSGL_V, "vo_pxa: preinit() was called\n"); /* fill pxa_priv_t information */ memset(priv, 0, sizeof(*priv)); priv->fd = -1; /* We need to open the base framebuffer device, to change and restore modes */ priv->base_fd = open( "/dev/fb0", O_RDWR ); if( priv->base_fd < 0 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: Could not open base framebuffer device\n"); return -1; } /* Get the base fb var data, so we can restore if we change video modes */ rc = ioctl( priv->base_fd, FBIOGET_VSCREENINFO, &(priv->base_orig_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: FBIOGET_VSCREENINFO preinit base_fd failed %d\n", errno ); /* If this failed, close down the FD so we don't try to set this again */ close( priv->base_fd ); priv->base_fd = -1; return -1; } return 0; } /***************************************************************************** * config * * Config the display driver. * params: * src_width,srcheight: image source size * dst_width,dst_height: size of the requested window size, just a hint * fullscreen: flag, 0=windowd 1=fullscreen, just a hint * title: window title, if available * format: fourcc of pixel format * returns : zero on successful initialization, non-zero on error. * ****************************************************************************/ static int config(uint32_t src_width, uint32_t src_height, uint32_t dst_width, uint32_t dst_height, uint32_t flags, char *title, uint32_t format) { pxa_priv_t *priv = &st_pxa_priv; int rc; int i; mp_msg(MSGT_VO, MSGL_V, "vo_pxa: config() src_width:%d, src_height:%d, dst_width:%d, dst_height:%d\n", src_width, src_height, dst_width, dst_height); /* Check format */ if( !vo_pxa_query_format(format) ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: unsupported fourcc for this driver: %x (%s)\n", format, vo_format_name(format) ); goto err_out; } priv->format = format; /* Change resolution? */ priv->vm = flags & VOFLAG_MODESWITCHING; if( priv->vm ) { priv->my_fb_var = priv->base_orig_fb_var; /* Hard coded values suck, never mind */ priv->my_fb_var.xres = 240; priv->my_fb_var.yres = 320; priv->my_fb_var.pixclock = 134617; priv->my_fb_var.left_margin = 20; priv->my_fb_var.right_margin = 46; priv->my_fb_var.upper_margin = 1; priv->my_fb_var.lower_margin = 0; priv->my_fb_var.hsync_len = 20; priv->my_fb_var.vsync_len = 2; rc = ioctl( priv->base_fd, FBIOPUT_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() Set FBIOPUT_VSCREENINFO on base_fd failed %d\n", errno ); priv->vm = 0; goto err_out; } /* We need this sleep, to make the change in resolution actually happen, before we open the overlay */ sleep(1); } /* Open up the overlay fbdev */ priv->fd = open( "/dev/fb2", O_RDWR ); if( priv->fd < 0 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: Could not open /dev/fb2: %d\n", errno ); goto err_out; } /* Read in fb var data */ rc = ioctl( priv->fd, FBIOGET_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() FBIOGET_VSCREENINFO from fd failed %d\n", errno ); goto err_out; } /* Store away the source dimensions, so we can place in centre of screen later in vm mode */ priv->src_width = src_width; priv->src_height = src_height; /* Set up the buffer */ if( priv->vm ) { /* Ignore size, as the rest of the screen is toast. Use max size */ priv->my_fb_var.xres = 240; priv->my_fb_var.yres = 320; /* Do we need to rotate? */ if( priv->src_width > priv->src_height ) { /* Yes */ priv->rotate = 1; } priv->width = 240; priv->height = 320; } else { priv->my_fb_var.xres = src_width; priv->my_fb_var.yres = src_height; priv->width = src_width; priv->height = src_height; } priv->my_fb_var.nonstd = ( 4 << 20) /* Format YV12 */ | ( 0 << 0) /* x position */ | ( 0 << 10); /* y position */ /* We have to set the bits per pixel to a valid value, even though it is * incorrect for YV12 */ priv->my_fb_var.bits_per_pixel = 16; rc = ioctl( priv->fd, FBIOPUT_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() FBIOPUT_VSCREENINFO to fd failed: %d\n", errno ); goto err_out; } /* Next get the fixed fbvars, so we can mmap the data for all 3 planes */ rc = ioctl( priv->fd, FBIOGET_FSCREENINFO, &(priv->my_fb_fix) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() FBIOGET_FSCREENINFO from fd failed: %d\n", errno ); goto err_out; } priv->fb_mem_base = mmap( NULL, priv->my_fb_fix.smem_len, (PROT_READ | PROT_WRITE ), MAP_SHARED, priv->fd, 0 ); if( priv->fb_mem_base == MAP_FAILED ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: mmap fd buffer failed: %d\n", errno ); goto err_out; } /* Finally, find the offsets of each plane by getting the var data again */ rc = ioctl( priv->fd, FBIOGET_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() FBIOGET_VSCREENINFO from fd (2) failed %d\n", errno ); goto err_out; } /* Fill the overlay with black */ memset( priv->fb_mem_base + priv->my_fb_var.red.offset, 16, priv->my_fb_var.red.length ); memset( priv->fb_mem_base + priv->my_fb_var.green.offset, 128, priv->my_fb_var.green.length ); memset( priv->fb_mem_base + priv->my_fb_var.blue.offset, 128, priv->my_fb_var.blue.length ); /* Now open the OSD overlay - overlay 1, and fill with transparent */ sleep( 1 ); priv->overlay_fd = open( "/dev/fb1", O_RDWR ); if( priv->overlay_fd < 0 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: Could not open /dev/fb1: %d\n", errno ); goto err_out; } /* Read in fb var data */ rc = ioctl( priv->overlay_fd, FBIOGET_VSCREENINFO, &(priv->osd_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() FBIOGET_VSCREENINFO from overlay_fd failed; %d\n", errno ); goto err_out; } priv->osd_fb_var.xres = priv->width; priv->osd_fb_var.yres = priv->height; priv->osd_fb_var.nonstd = ( 0 << 0) /* x position */ | ( 0 << 10); /* y position */ /* Use 15 bit mode, with top bit transparency */ priv->osd_fb_var.bits_per_pixel = 16; rc = ioctl( priv->overlay_fd, FBIOPUT_VSCREENINFO, &(priv->osd_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() FBIOPUT_VSCREENINFO to overlay_fd failed: %d\n", errno ); goto err_out; } /* Next get the fixed fbvars, so we can mmap the data */ rc = ioctl( priv->overlay_fd, FBIOGET_FSCREENINFO, &(priv->osd_fb_fix) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: config() FBIOGET_FSCREENINFO from overlay_fd failed %d\n", errno ); goto err_out; } priv->osd_mem_base = mmap( NULL, priv->osd_fb_fix.smem_len, (PROT_READ | PROT_WRITE ), MAP_SHARED, priv->overlay_fd, 0 ); if( priv->osd_mem_base == MAP_FAILED ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: mmap osd_mem_base failed: %d\n", errno ); goto err_out; } /* Fill the overlay with transparent */ vo_pxa_clear_osd( priv->osd_mem_base, priv->osd_fb_fix.smem_len ); /* We are good to go! */ mp_msg( MSGT_VO, MSGL_V, "vo_pxa: Opened video overlay %d x %d fourcc %s\n", priv->my_fb_var.xres, priv->my_fb_var.yres, vo_format_name(format) ); return 0; err_out: /* Don't do anything here for the moment */ return -1; } /***************************************************************************** * * control * * Control display * ****************************************************************************/ static int control(uint32_t request, void *data, ...) { mp_msg(MSGT_VO, MSGL_V, "vo_pxa: control %08x\n", request ); switch( request ) { case VOCTRL_QUERY_FORMAT: return( vo_pxa_query_format( *(uint32_t *)data ) ); break; } return VO_NOTIMPL; } /***************************************************************************** * * draw_frame * * Display a new RGB/BGR frame of the video to the screen. * params: * src[0] - pointer to the image * ****************************************************************************/ int draw_frame(uint8_t *src[]) { /* This is not implimented */ mp_msg(MSGT_VO, MSGL_ERR, "vo_pxa: dummy draw_frame() was called\n"); return -1; } /***************************************************************************** * * draw_slice * * Draw a planar YUV slice to the buffer: * params: * src[3] = source image planes (Y,U,V) * stride[3] = source image planes line widths (in bytes) * w,h = width*height of area to be copied (in Y pixels) * x,y = position at the destination image (in Y pixels) * ****************************************************************************/ int draw_slice(uint8_t *src[], int stride[], int w,int h, int x,int y) { pxa_priv_t *priv = &st_pxa_priv; /* This routine is only display routine actually implimented */ mp_msg(MSGT_VO, MSGL_V, "vo_pxa: draw_slice() w %d h %d x %d y %d stride %d %d %d\n", w, h, x, y, stride[0], stride[1], stride[2] ); /* It would be faster to check if source and dest have same geometry and copy * whole block * For the moment we just copy a line at a time */ /* In vm mode rotate if wider than long */ if( priv->vm ) { /* Do we nee to rotate? */ if( priv->rotate ) { /* Yes, rotated version */ int dst_x_offset = 0; int dst_y_offset = 0; int src_x_offset = 0; int src_y_offset = 0; /* Figure out dst offset */ if( priv->src_width < 320 ) { dst_x_offset = ( ( 320 - priv->src_width ) / 2 ); /* Make it a multiple of 16 */ dst_x_offset &= ~(0xf); } if( priv->src_height < 240 ) { dst_y_offset = ( ( 240 - priv->src_height ) / 2 ); /* Make it a multiple of 16 */ dst_y_offset &= ~(0xf); } dst_x_offset += x; dst_y_offset += y; if( ( dst_x_offset >= 320 ) || ( dst_y_offset >= 240 ) ) { /* Nothing to do - drawing off the screen! */ return( 0 ); } /* Limit to drawable area */ if( ( w + dst_x_offset ) > 320 ) { w = ( 320 - dst_x_offset ); } if( ( h + dst_y_offset ) > 240 ) { h = ( 240 - dst_y_offset ); } /* And source offset */ if( priv->src_width > 320 ) { src_x_offset = ( ( priv->src_width - 320 ) / 2 ); /* Make it a multiple of 16 */ src_x_offset &= ~(0xf); } if( priv->src_height > 240 ) { src_y_offset = ( ( priv->src_height - 240 ) / 2 ); /* Make it a multiple of 16 */ src_y_offset &= ~(0xf); } /* Y first */ vo_pxa_copy_and_rotate( src[0] + src_x_offset + (src_y_offset * stride[0]), stride[0], priv->fb_mem_base + priv->my_fb_var.red.offset + (240 * dst_x_offset) + (240 - dst_y_offset - h), w, h, 240 ); /* Now U */ vo_pxa_copy_and_rotate( src[1] + src_x_offset/2 + (src_y_offset/2 * stride[1]), stride[1], priv->fb_mem_base + priv->my_fb_var.green.offset + (120 * dst_x_offset/2) + (120 - dst_y_offset/2 - h/2), w/2, h/2, 120 ); vo_pxa_copy_and_rotate( src[2] + src_x_offset/2 + (src_y_offset/2 * stride[2]), stride[2], priv->fb_mem_base + priv->my_fb_var.blue.offset + (120 * dst_x_offset/2) + (120 - dst_y_offset/2 - h/2), w/2, h/2, 120 ); } else { /* Don't rotate */ int i; uint8_t *my_src; uint8_t *dest; int dst_x_offset = 0; int dst_y_offset = 0; int src_x_offset = 0; int src_y_offset = 0; /* Figure out dst offset */ if( priv->src_width < 240 ) { dst_x_offset = ( ( 240 - priv->src_width ) / 2 ); /* Make it a multiple of 16 */ dst_x_offset &= ~(0xf); } if( priv->src_height < 320 ) { dst_y_offset = ( ( 320 - priv->src_height ) / 2 ); /* Make it a multiple of 16 */ dst_y_offset &= ~(0xf); } dst_x_offset += x; dst_y_offset += y; if( ( dst_x_offset >= 240 ) || ( dst_y_offset >= 320 ) ) { /* Nothing to do - drawing off the screen! */ return( 0 ); } /* Limit to drawable area */ if( ( w + dst_x_offset ) > 240 ) { w = ( 240 - dst_x_offset ); } if( ( h + dst_y_offset ) > 320 ) { h = ( 320 - dst_y_offset ); } /* And source offset */ if( priv->src_width > 240 ) { src_x_offset = ( ( priv->src_width - 240 ) / 2 ); /* Make it a multiple of 16 */ src_x_offset &= ~(0xf); } if( priv->src_height > 320 ) { src_y_offset = ( ( priv->src_height - 320 ) / 2 ); /* Make it a multiple of 16 */ src_y_offset &= ~(0xf); } /* First Y */ for( i = 0; ifb_mem_base + priv->my_fb_var.red.offset + ( (dst_y_offset+i) * priv->my_fb_fix.line_length ) + dst_x_offset; my_src = src[0] + src_x_offset + (stride[0] * (i+src_y_offset)); memcpy( dest, my_src, w ); } /* Now U */ for( i = 0; i<(h/2); i++ ) { dest = priv->fb_mem_base + priv->my_fb_var.green.offset + ( ((dst_y_offset/2)+i) * (priv->my_fb_fix.line_length/2) ) + dst_x_offset/2; my_src = src[1] + src_x_offset/2 + (stride[1] * (i+(src_y_offset/2))); memcpy( dest, my_src, w/2 ); } /* Finaly V */ for( i = 0; i<(h/2); i++ ) { dest = priv->fb_mem_base + priv->my_fb_var.blue.offset + ( ((dst_y_offset/2)+i) * (priv->my_fb_fix.line_length/2) ) + dst_x_offset/2; my_src = src[2] + src_x_offset/2 + (stride[2] * (i+(src_y_offset/2))); memcpy( dest, my_src, w/2 ); } } } else { /* Not full screen mode */ uint8_t *my_src; uint8_t *dest; size_t length; int i; /* It would be faster to check if source and dest have same geometry and copy * whole block * For the moment we just copy a line at a time */ /* Limit area written to */ if( x >= priv->my_fb_fix.line_length ) { return 0; } if( w + x > priv->my_fb_fix.line_length ) { w = priv->my_fb_fix.line_length - x; } if( y>= priv->my_fb_var.yres ) { return 0; } if( h + y > priv->my_fb_var.yres ) { h = priv->my_fb_var.yres - y; } /* First Y */ for( i = 0; ifb_mem_base + priv->my_fb_var.red.offset + ( (y+i) * priv->my_fb_fix.line_length ) + x; my_src = src[0] + stride[0] * i; memcpy( dest, my_src, w ); } /* Now U */ for( i = 0; i<(h/2); i++ ) { dest = priv->fb_mem_base + priv->my_fb_var.green.offset + ( ((y/2)+i) * (priv->my_fb_fix.line_length/2) ) + x; my_src = src[1] + stride[1] * i; memcpy( dest, my_src, w/2 ); } /* Finaly V */ for( i = 0; i<(h/2); i++ ) { dest = priv->fb_mem_base + priv->my_fb_var.blue.offset + ( ((y/2)+i) * (priv->my_fb_fix.line_length/2) ) + x; my_src = src[2] + stride[2] * i; memcpy( dest, my_src, w/2 ); } } return 0; } static void draw_osd(void) { pxa_priv_t *priv = &st_pxa_priv; int osd_has_changed; /* This gets called every frame, so systems which do the OSD without a * seperate overlay can mix in the image. We need to find out if the osd * has actually been updated! */ mp_msg(MSGT_VO, MSGL_V, "vo_pxa: draw_osd() was called\n"); osd_has_changed = vo_update_osd( priv->width, priv->height); if(osd_has_changed) { int i; mp_msg(MSGT_VO, MSGL_V, "vo_pxa: Clear and update OSD\n"); /* Fill with transparent */ vo_pxa_clear_osd( priv->osd_mem_base, priv->osd_fb_fix.smem_len ); priv->osd_cleared = 1; /* now update */ if( priv->rotate ) { vo_draw_text( priv->width, priv->height, vo_pxa_draw_alpha_with_rotate ); } else { vo_draw_text( priv->width, priv->height, vo_pxa_draw_alpha ); } } } /***************************************************************************** * * flip_page * * Blit/Flip buffer to the screen. Must be called after each frame! * * ****************************************************************************/ static void flip_page(void) { mp_msg(MSGT_VO, MSGL_V, "vo_pxa: flip_page() was called\n"); } /***************************************************************************** * * check_events * * ****************************************************************************/ static void check_events(void) { mp_msg(MSGT_VO, MSGL_V, "vo_pxa: check_events() was called\n"); } /***************************************************************************** * * uninit * * ****************************************************************************/ static void uninit(void) { pxa_priv_t *priv = &st_pxa_priv; int rc; mp_msg(MSGT_VO, MSGL_V, "vo_pxa: uninit() was called\n"); if( priv->vm ) { /* We need these sleeps, to make the change in resolution actually happen */ sleep(1); /* Restore original resolution */ if( priv->base_fd >= 0 ) { rc = ioctl( priv->base_fd, FBIOPUT_VSCREENINFO, &(priv->base_orig_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: uninit() FBIOPUT_VSCREENINFO to base_fd failed %d\n", errno ); } } /* We need these sleeps, to make the change in resolution actually happen */ /* For some reason, if we change the reolution the overlay buffer never gets deleted? */ sleep(1); } /* We need to force the overlays to be really disabled, otherwise they * will come back as zombies after suspend, resume * This trick seems to work, but will not be needed once kernel driver * is fixed */ if( priv->fd >= 0 ) { rc = ioctl( priv->fd, FBIOGET_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: uninit() FBIOGET_VSCREENINFO from fd failed %d\n", errno ); } priv->my_fb_var.bits_per_pixel = 0; rc = ioctl( priv->fd, FBIOPUT_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: uninit() FBIOPUT_VSCREENINFO from fd failed %d\n", errno ); } } if( priv->overlay_fd >= 0 ) { rc = ioctl( priv->overlay_fd, FBIOGET_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: uninit() FBIOGET_VSCREENINFO from overlay_fd failed %d\n", errno ); } priv->my_fb_var.bits_per_pixel = 0; rc = ioctl( priv->overlay_fd, FBIOPUT_VSCREENINFO, &(priv->my_fb_var) ); if( rc == -1 ) { mp_msg( MSGT_VO, MSGL_ERR, "vo_pxa: uninit() FBIOPUT_VSCREENINFO from overlay_fd failed %d\n", errno ); } } if( priv->base_fd >= 0 ) { close( priv->base_fd ); priv->base_fd = -1; } } /***************************************************************************** * * Internal functions, not part of mplayer API * ****************************************************************************/ static int vo_pxa_query_format( uint32_t format ) { mp_msg(MSGT_VO, MSGL_V, "vo_pxa: vo_pxa_query_format was called: %x (%s)\n", format, vo_format_name(format)); switch (format) { /* Planar YUV Formats */ /* Warning! dropthrough */ case IMGFMT_YV12: case IMGFMT_IYUV: case IMGFMT_I420: return( VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_OSD | VFCAP_ACCEPT_STRIDE ); break; } return 0; } static void vo_pxa_copy_and_rotate( uint8_t *src, int stride, uint8_t *dst, int w, int h, int dst_stride ) { int i,j; uint8_t *my_src, *my_dst; Vo_Pxa_Pixel_Data8 *img_dst_pixel_data8; /* Loop so writing consectuive data in rotated image */ /* This produces some pretty good assembler - better than the handcoded stuff in w100 */ for( j=0; josd_mem_base + x + ( priv->width * ( y + i ) ); src_ptr = src + ( i * stride ); a_ptr = srca + ( i * stride ); for( j=0; j> 3; *out_ptr++ = grey | (grey << 5) | (grey<<10); } else { *out_ptr++ = 0x8000; src_ptr++; } } } } static void vo_pxa_draw_alpha_with_rotate( int x, int y, int w, int h, unsigned char *src, unsigned char *srca, int stride ) { /* Dump data into our 15bit buffer with transparency */ pxa_priv_t *priv = &st_pxa_priv; int i,j; unsigned char *src_ptr = src; unsigned char *a_ptr = srca; unsigned short *out_ptr; mp_msg(MSGT_VO, MSGL_V, "vo_pxa: vo_pxa_draw_alpha_with_rotate() x %d y %d w %d h %d\n", x, y, w, h ); if( x >= 320 ) { /* Off the screen */ return; } /* Limit to size of screen/memory */ if( ( w + x ) > 320 ) { w = 320 - x; } if( y >= 240 ) { /* Off the screen */ return; } /* Limit to size of screen/memory */ if( ( y + h ) > 240 ) { h = 240 - y; } /* We ignore the alpha channel, other than off or on */ for( i=0; iosd_mem_base + y + ( priv->width * ( x + i ) ); src_ptr = src + i + ( stride * (h - 1)); a_ptr = srca + i + ( stride * (h - 1)); for( j=0; j> 3; *out_ptr++ = grey | (grey << 5) | (grey<<10); } else { *out_ptr++ = 0x8000; src_ptr; } a_ptr -= stride; src_ptr -= stride; } } } static void vo_pxa_clear_osd( uint16_t *mem_base, int len ) { /* fill whole area with 0x8000 -> trsnaparent. * assume area is word aligned, and a mulitple of 16 bytes in length * However I tried I could not get the compiler to generate this. * It always wanted to to do ldmia 4 words from stack followed by * stmia 4 words. This seems odd! */ __asm__ __volatile__ ( "mov r4, %0 \n\t" "mov r5, %1, lsr #4 \n\t" "subs r5, r5, #1\n\t" "mov r0, #0x80000000 \n\t" "orr r0, r0, #0x00008000 \n\t" "mov r1, r0 \n\t" "mov r2, r0 \n\t" "mov r3, r0 \n\t" "1: \n\t" "subs r5, r5, #1\n\t" "stmia r4!, {r0, r1, r2, r3} \n\t" "bne 1b \n\t" : : "r"(mem_base), "r"(len) : "memory", "r0", "r1", "r2", "r3", "r4", "r5", "cc" ); }