Merged from external project:
[bertos.git] / bertos / drv / lcd_text.c
1 /**
2  * \file
3  * <!--
4  * This file is part of BeRTOS.
5  *
6  * Bertos is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * As a special exception, you may use this file as part of a free software
21  * library without restriction.  Specifically, if other files instantiate
22  * templates or use macros or inline functions from this file, or you compile
23  * this file and link it with other files to produce an executable, this
24  * file does not by itself cause the resulting executable to be covered by
25  * the GNU General Public License.  This exception does not however
26  * invalidate any other reasons why the executable file might be covered by
27  * the GNU General Public License.
28  *
29  * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
30  * All Rights Reserved.
31  * -->
32  *
33  * \brief Generic text LCD driver (impl.).
34  *
35  * \version $Id$
36  * \author Bernie Innocenti <bernie@codewiz.org>
37  * \author Stefano Fedrigo <aleph@develer.com>
38  */
39
40 #include "lcd_text.h"
41 #include "lcd_hd44.h"
42
43 #include <cfg/macros.h> // BV()
44 #include <cfg/debug.h>
45
46 #include <drv/timer.h> // timer_delay()
47
48 #include <mware/formatwr.h> // _formatted_write()
49 #include <struct/list.h> // LIST_EMPTY()
50
51 #include <string.h> // strlen()
52
53
54 /** Maximum number of layers. */
55 #define LCD_LAYERS 6
56
57 #if CONFIG_KERN
58         #include <kern/sem.h>
59         /** Semaphore to arbitrate access to the display. */
60         static struct Semaphore lcd_semaphore;
61         #define LOCK_LCD        sem_obtain(&lcd_semaphore)
62         #define UNLOCK_LCD      sem_release(&lcd_semaphore)
63 #else /* !CONFIG_KERN */
64         #define LOCK_LCD        do {} while (0)
65         #define UNLOCK_LCD      do {} while (0)
66 #endif /* !CONFIG_KERN */
67
68 DECLARE_LIST_TYPE(Layer);
69
70 Layer *lcd_DefLayer;
71 static Layer lcd_LayersPool[LCD_LAYERS];
72 static LIST_TYPE(Layer) lcd_Layers;
73 static LIST_TYPE(Layer) lcd_FreeLayers;
74
75 /**
76  * Current cursor status.
77  *
78  * One of LCD_CMD_CURSOR_OFF, LCD_CMD_CURSOR_BLOCK or LCD_CMD_CURSOR_LINE.
79  */
80 static uint8_t lcd_CursorStatus;
81
82 /** Current cursor position, encoded as a Cursor position and status. */
83 static lcdpos_t lcd_CursorAddr;
84
85
86 void lcd_setAddr(Layer *layer, lcdpos_t addr)
87 {
88         /* Sanity check: wrap around to display limits */
89         while (addr >= LCD_ROWS * LCD_COLS)
90                 addr -= LCD_ROWS * LCD_COLS;
91
92         layer->addr = addr;
93 }
94
95 #if CONFIG_KERN
96
97 void lcd_lock(void)
98 {
99         LOCK_LCD;
100 }
101
102
103 void lcd_unlock(void)
104 {
105         UNLOCK_LCD;
106 }
107
108 #endif /* CONFIG_KERN */
109
110
111 /**
112  * Write one character to the display at the current
113  * cursor prosition, then move the cursor right. The
114  * cursor is wrapped to the next line when it moves
115  * beyond the end of the current line.
116  *
117  * \note Does _NOT_ lock the display semaphore.
118  */
119 static void lcd_putCharUnlocked(char c, Layer *layer)
120 {
121         Layer *l2;
122         lcdpos_t addr = layer->addr;
123
124         /* Store character in layer buffer */
125         layer->buf[addr] = c;
126
127         /* Move to next character */
128         if (++layer->addr >= LCD_COLS * LCD_ROWS)
129                 layer->addr = 0;
130
131         /* Do not write on LCD if layer is hidden. */
132         if (layer->pri == LAYER_HIDDEN)
133                 return;
134
135         /*
136          * Check if this location is obscured by
137          * other layers above us.
138          */
139         for (l2 = layer->pred; l2->pred; l2 = l2->pred)
140         {
141                 if (l2->buf[addr])
142                 {
143                         /* DB(kprintf("layer %04x obs %04x at %d\n", l2, layer, addr);) */
144                         return;
145                 }
146         }
147
148         /* Write character */
149         if (c)
150                 lcd_putc(addr, c);
151         else
152                 /* FIXME: should look for layers beneath! */
153                 lcd_putc(addr, ' ');
154 }
155
156
157 void lcd_putChar(char c, Layer *layer)
158 {
159         LOCK_LCD;
160         lcd_putCharUnlocked(c, layer);
161         UNLOCK_LCD;
162 }
163
164 void lcd_layerSet(Layer *layer, char c)
165 {
166         int i;
167
168         LOCK_LCD;
169         lcd_setAddr(layer, 0);
170         for (i = 0; i < LCD_COLS * LCD_ROWS; i++)
171                 lcd_putCharUnlocked(c, layer);
172         UNLOCK_LCD;
173 }
174
175
176 void lcd_clear(Layer *layer)
177 {
178         lcd_layerSet(layer, 0);
179 }
180
181
182 void lcd_clearLine(Layer *layer, int y)
183 {
184         int i;
185
186         LOCK_LCD;
187         lcd_setAddr(layer, LCD_POS(0, y));
188         for (i = 0; i < LCD_COLS; i++)
189                 lcd_putCharUnlocked(0, layer);
190         UNLOCK_LCD;
191 }
192
193
194 void lcd_moveCursor(lcdpos_t addr)
195 {
196         LOCK_LCD;
197         lcd_moveTo(addr);
198         UNLOCK_LCD;
199 }
200
201
202 char lcd_setCursor(char mode)
203 {
204         static const char cursor_cmd[3] =
205         {
206                 LCD_CMD_CURSOR_OFF, LCD_CMD_CURSOR_BLOCK, LCD_CMD_CURSOR_LINE
207         };
208         char oldmode = lcd_CursorStatus;
209
210         LOCK_LCD;
211         lcd_CursorStatus = mode;
212         lcd_setReg(cursor_cmd[(int)mode]);
213         if (mode)
214                 lcd_moveCursor(lcd_CursorAddr);
215         UNLOCK_LCD;
216
217         return oldmode;
218 }
219
220
221 int lcd_vprintf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, va_list ap)
222 {
223         int len;
224
225         LOCK_LCD;
226
227         /*
228          * Se il cursore era acceso, spegnilo durante
229          * l'output per evitare che salti alla posizione
230          * in cui si scrive.
231          */
232         if (lcd_CursorStatus)
233                 lcd_setReg(LCD_CMD_CURSOR_OFF);
234
235         /* Spostamento del cursore */
236         lcd_setAddr(layer, addr);
237
238         if (mode & LCD_CENTER)
239         {
240                 int pad;
241
242                 /*
243                  * NOTE: calculating the string lenght BEFORE it gets
244                  * printf()-formatted. Real lenght may differ.
245                  */
246                 pad = (LCD_COLS - strlen(format)) / 2;
247                 while (pad--)
248                         lcd_putCharUnlocked(' ', layer);
249         }
250
251         len = _formatted_write(format, (void (*)(char, void *))lcd_putCharUnlocked, layer, ap);
252
253         if (mode & (LCD_FILL | LCD_CENTER))
254                 while (layer->addr % LCD_COLS)
255                         lcd_putCharUnlocked(' ', layer);
256
257         /*
258          * Riaccendi il cursore e riportalo alla
259          * vecchia posizione
260          */
261         if (lcd_CursorStatus)
262                 lcd_setCursor(lcd_CursorStatus);
263
264         UNLOCK_LCD;
265
266         return len;
267 }
268
269
270 int lcd_printf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, ...)
271 {
272         int len;
273         va_list ap;
274
275         va_start(ap, format);
276         len = lcd_vprintf(layer, addr, mode, format, ap);
277         va_end(ap);
278
279         return len;
280 }
281
282
283 /**
284  * Internal function to move a layer between two positions.
285  *
286  * \note The layer must be *already* enqueued in some list.
287  * \note The display must be already locked!
288  */
289 static void lcd_enqueueLayer(Layer *layer, char pri)
290 {
291         Layer *l2;
292
293         /* Remove layer from whatever list it was in before */
294         REMOVE(layer);
295
296         layer->pri = pri;
297
298         /*
299          * Search for the first layer whose priority
300          * is less or equal to the layer we are adding.
301          */
302         FOREACH_NODE(l2, &lcd_Layers)
303                 if (l2->pri <= pri)
304                         break;
305
306         /* Enqueue layer */
307         INSERT_BEFORE(layer, l2);
308 }
309
310 Layer *lcd_newLayer(char pri)
311 {
312         Layer *layer;
313
314         LOCK_LCD;
315
316         if (LIST_EMPTY(&lcd_FreeLayers))
317         {
318                 UNLOCK_LCD;
319                 //ASSERT(false);
320                 return NULL;
321         }
322
323         layer = (Layer *)LIST_HEAD(&lcd_FreeLayers);
324         layer->addr = 0;
325         memset(layer->buf, 0, LCD_ROWS * LCD_COLS);
326
327         lcd_enqueueLayer(layer, pri);
328
329         UNLOCK_LCD;
330         return layer;
331 }
332
333 /**
334  * Redraw the display (internal).
335  *
336  * \note The display must be already locked.
337  */
338 static void lcd_refresh(void)
339 {
340         lcdpos_t addr;
341         Layer *l;
342
343         for (addr = 0; addr < LCD_ROWS * LCD_COLS; ++addr)
344         {
345                 FOREACH_NODE(l, &lcd_Layers)
346                 {
347                         //kprintf("%d %x %p\n", addr, l->buf[0], l);
348                         if (l->pri == LAYER_HIDDEN)
349                                 break;
350
351                         if (l->buf[addr])
352                         {
353                                 /* Refresh location */
354                                 lcd_putc(addr, l->buf[addr]);
355                                 goto done;
356                         }
357                 }
358
359                 /* Draw background */
360                 lcd_putc(addr, ' ');
361         done:
362                 ;
363         }
364 }
365
366 /**
367  * Rearrange layer depth and refresh display accordingly.
368  *
369  * \note Setting a priority of LAYER_HIDDEN makes the layer invisible.
370  */
371 void lcd_setLayerDepth(Layer *layer, char pri)
372 {
373         if (pri != layer->pri)
374         {
375                 LOCK_LCD;
376                 lcd_enqueueLayer(layer, pri);
377                 /* Vile but simple */
378                 lcd_refresh();
379                 UNLOCK_LCD;
380         }
381 }
382
383 void lcd_deleteLayer(Layer *layer)
384 {
385         LOCK_LCD;
386
387 /* We use lcd_refresh() instead.  Much simpler than this mess, but slower. */
388 #if 0
389         Layer *l2;
390         lcdpos_t addr;
391
392         /* Repair damage on underlaying layers */
393         for (addr = 0; addr < LCD_ROWS * LCD_COLS; ++addr)
394         {
395                 /* If location was covered by us */
396                 if (layer->buf[addr])
397                 {
398                         /* ...and it wasn't covered by others above us... */
399                         for (l2 = layer->pred; l2->pred; l2 = l2->pred)
400                                 if (l2->buf[addr])
401                                         /* can't just break here! */
402                                         goto not_visible;
403
404                         /* ...scan underlaying layers to repair damage */
405                         for (l2 = layer->succ; l2->succ; l2 = l2->succ)
406                                 if (l2->buf[addr])
407                                 {
408                                         /* Refresh character */
409                                         lcd_putc(addr, l2->buf[addr]);
410
411                                         /* No need to search on deeper layers */
412                                         break;
413                                 }
414
415                         not_visible:
416                                 ;
417                 }
418         }
419 #endif
420
421         // Remove layer from lcd_Layers list.
422         REMOVE(layer);
423
424         /* Put layer back into free list */
425         ADDHEAD(&lcd_FreeLayers, layer);
426
427         lcd_refresh();
428
429         UNLOCK_LCD;
430 }
431
432
433 static void lcd_setDefLayer(Layer *layer)
434 {
435         lcd_DefLayer = layer;
436 }
437
438 #include <cfg/debug.h>
439 void lcd_init(void)
440 {
441         int i;
442
443         LIST_INIT(&lcd_Layers);
444         LIST_INIT(&lcd_FreeLayers);
445         for (i = 0; i < LCD_LAYERS; ++i)
446                 ADDHEAD(&lcd_FreeLayers, &lcd_LayersPool[i]);
447
448         lcd_setDefLayer(lcd_newLayer(0));
449
450         lcd_hw_init();
451
452         lcd_setCursor(0);
453 }
454
455