fbdev: fix clear_lines
[libpicofe.git] / linux / fbdev.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <sys/ioctl.h>
8 #include <sys/mman.h>
9 #include <unistd.h>
10 #include <linux/fb.h>
11 #include <linux/matroxfb.h>
12
13 #include "fbdev.h"
14
15 #define FBDEV_MAX_BUFFERS 3
16
17 struct vout_fbdev {
18         int     fd;
19         void    *mem;
20         size_t  mem_size;
21         struct  fb_var_screeninfo fbvar_old;
22         struct  fb_var_screeninfo fbvar_new;
23         int     buffer_write;
24         int     fb_size;
25         int     buffer_count;
26         int     top_border, bottom_border;
27 };
28
29 void *vout_fbdev_flip(struct vout_fbdev *fbdev)
30 {
31         int draw_buf;
32
33         if (fbdev->buffer_count < 2)
34                 return fbdev->mem;
35
36         draw_buf = fbdev->buffer_write;
37         fbdev->buffer_write++;
38         if (fbdev->buffer_write >= fbdev->buffer_count)
39                 fbdev->buffer_write = 0;
40
41         fbdev->fbvar_new.yoffset = 
42                 (fbdev->top_border + fbdev->fbvar_new.yres + fbdev->bottom_border) * draw_buf +
43                 fbdev->top_border;
44
45         ioctl(fbdev->fd, FBIOPAN_DISPLAY, &fbdev->fbvar_new);
46
47         return (char *)fbdev->mem + fbdev->fb_size * fbdev->buffer_write;
48 }
49
50 void vout_fbdev_wait_vsync(struct vout_fbdev *fbdev)
51 {
52         int arg = 0;
53         ioctl(fbdev->fd, FBIO_WAITFORVSYNC, &arg);
54 }
55
56 int vout_fbdev_resize(struct vout_fbdev *fbdev, int w, int h,
57                       int left_border, int right_border, int top_border, int bottom_border, int no_dblbuf)
58 {
59         int w_total = left_border + w + right_border;
60         int h_total = top_border + h + bottom_border;
61         size_t mem_size;
62         int ret;
63
64         // unblank to be sure the mode is really accepted
65         ioctl(fbdev->fd, FBIOBLANK, FB_BLANK_UNBLANK);
66
67         if (fbdev->fbvar_new.bits_per_pixel != 16 ||
68                         w != fbdev->fbvar_new.xres ||
69                         h != fbdev->fbvar_new.yres ||
70                         w_total != fbdev->fbvar_new.xres_virtual ||
71                         h_total > fbdev->fbvar_new.yres_virtual ||
72                         left_border != fbdev->fbvar_new.xoffset) {
73                 fbdev->fbvar_new.xres = w;
74                 fbdev->fbvar_new.yres = h;
75                 fbdev->fbvar_new.xres_virtual = w_total;
76                 fbdev->fbvar_new.yres_virtual = h_total;
77                 fbdev->fbvar_new.xoffset = left_border;
78                 fbdev->fbvar_new.bits_per_pixel = 16;
79                 printf(" switching to %dx%d@16\n", w, h);
80                 ret = ioctl(fbdev->fd, FBIOPUT_VSCREENINFO, &fbdev->fbvar_new);
81                 if (ret == -1) {
82                         perror("FBIOPUT_VSCREENINFO ioctl");
83                         return -1;
84                 }
85         }
86
87         fbdev->buffer_count = FBDEV_MAX_BUFFERS; // be optimistic
88         if (no_dblbuf)
89                 fbdev->buffer_count = 1;
90
91         if (fbdev->fbvar_new.yres_virtual < h_total * fbdev->buffer_count) {
92                 fbdev->fbvar_new.yres_virtual = h_total * fbdev->buffer_count;
93                 ret = ioctl(fbdev->fd, FBIOPUT_VSCREENINFO, &fbdev->fbvar_new);
94                 if (ret == -1) {
95                         fbdev->buffer_count = 1;
96                         fprintf(stderr, "Warning: failed to increase virtual resolution, "
97                                         "doublebuffering disabled\n");
98                 }
99         }
100
101         fbdev->fb_size = w_total * h_total * 2;
102         fbdev->top_border = top_border;
103         fbdev->bottom_border = bottom_border;
104
105         mem_size = fbdev->fb_size * fbdev->buffer_count;
106         if (fbdev->mem_size >= mem_size)
107                 return 0;
108
109         if (fbdev->mem != NULL)
110                 munmap(fbdev->mem, fbdev->mem_size);
111
112         fbdev->mem = mmap(0, mem_size, PROT_WRITE|PROT_READ, MAP_SHARED, fbdev->fd, 0);
113         if (fbdev->mem == MAP_FAILED && fbdev->buffer_count > 1) {
114                 fprintf(stderr, "Warning: can't map %zd bytes, doublebuffering disabled\n", fbdev->mem_size);
115                 fbdev->buffer_count = 1;
116                 mem_size = fbdev->fb_size;
117                 fbdev->mem = mmap(0, mem_size, PROT_WRITE|PROT_READ, MAP_SHARED, fbdev->fd, 0);
118         }
119         if (fbdev->mem == MAP_FAILED) {
120                 fbdev->mem = NULL;
121                 fbdev->mem_size = 0;
122                 perror("mmap framebuffer");
123                 return -1;
124         }
125
126         fbdev->mem_size = mem_size;
127         return 0;
128 }
129
130 void vout_fbdev_clear(struct vout_fbdev *fbdev)
131 {
132         memset(fbdev->mem, 0, fbdev->mem_size);
133 }
134
135 void vout_fbdev_clear_lines(struct vout_fbdev *fbdev, int y, int count)
136 {
137         int stride = fbdev->fbvar_new.xres_virtual * fbdev->fbvar_new.bits_per_pixel / 8;
138         int i;
139
140         if (y + count > fbdev->top_border + fbdev->fbvar_new.yres)
141                 count = fbdev->top_border + fbdev->fbvar_new.yres - y;
142
143         if (y >= 0 && count > 0)
144                 for (i = 0; i < fbdev->buffer_count; i++)
145                         memset((char *)fbdev->mem + fbdev->fb_size * i + y * stride, 0, stride * count);
146 }
147
148 int vout_fbdev_get_fd(struct vout_fbdev *fbdev)
149 {
150         return fbdev->fd;
151 }
152
153 struct vout_fbdev *vout_fbdev_init(const char *fbdev_name, int *w, int *h, int no_dblbuf)
154 {
155         struct vout_fbdev *fbdev;
156         int req_w, req_h;
157         int ret;
158
159         fbdev = calloc(1, sizeof(*fbdev));
160         if (fbdev == NULL)
161                 return NULL;
162
163         fbdev->fd = open(fbdev_name, O_RDWR);
164         if (fbdev->fd == -1) {
165                 fprintf(stderr, "%s: ", fbdev_name);
166                 perror("open");
167                 goto fail_open;
168         }
169
170         ret = ioctl(fbdev->fd, FBIOGET_VSCREENINFO, &fbdev->fbvar_old);
171         if (ret == -1) {
172                 perror("FBIOGET_VSCREENINFO ioctl");
173                 goto fail;
174         }
175
176         fbdev->fbvar_new = fbdev->fbvar_old;
177
178         req_w = fbdev->fbvar_new.xres;
179         if (*w != 0)
180                 req_w = *w;
181         req_h = fbdev->fbvar_new.yres;
182         if (*h != 0)
183                 req_h = *h;
184
185         ret = vout_fbdev_resize(fbdev, req_w, req_h, 0, 0, 0, 0, no_dblbuf);
186         if (ret != 0)
187                 goto fail;
188
189         printf("%s: %ix%i@%d\n", fbdev_name, fbdev->fbvar_new.xres, fbdev->fbvar_new.yres,
190                 fbdev->fbvar_new.bits_per_pixel);
191         *w = fbdev->fbvar_new.xres;
192         *h = fbdev->fbvar_new.yres;
193
194         memset(fbdev->mem, 0, fbdev->mem_size);
195
196         // some checks
197         ret = 0;
198         ret = ioctl(fbdev->fd, FBIO_WAITFORVSYNC, &ret);
199         if (ret != 0)
200                 fprintf(stderr, "Warning: vsync doesn't seem to be supported\n");
201
202         if (fbdev->buffer_count > 1) {
203                 fbdev->buffer_write = 0;
204                 fbdev->fbvar_new.yoffset = fbdev->fbvar_new.yres * (fbdev->buffer_count - 1);
205                 ret = ioctl(fbdev->fd, FBIOPAN_DISPLAY, &fbdev->fbvar_new);
206                 if (ret != 0) {
207                         fbdev->buffer_count = 1;
208                         fprintf(stderr, "Warning: can't pan display, doublebuffering disabled\n");
209                 }
210         }
211
212         printf("fbdev initialized.\n");
213         return fbdev;
214
215 fail:
216         close(fbdev->fd);
217 fail_open:
218         free(fbdev);
219         return NULL;
220 }
221
222 void vout_fbdev_finish(struct vout_fbdev *fbdev)
223 {
224         ioctl(fbdev->fd, FBIOPUT_VSCREENINFO, &fbdev->fbvar_old);
225         if (fbdev->mem != MAP_FAILED)
226                 munmap(fbdev->mem, fbdev->mem_size);
227         if (fbdev->fd >= 0)
228                 close(fbdev->fd);
229         fbdev->mem = NULL;
230         fbdev->fd = -1;
231         free(fbdev);
232 }
233