make uart_pty threadsafe
[simavr] / examples / board_hd77480 / avr_hd44780.c
1 /*
2  * ----------------------------------------------------------------------------
3  * "THE BEER-WARE LICENSE" (Revision 42):
4  * <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
5  * can do whatever you want with this stuff. If we meet some day, and you think
6  * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
7  * ----------------------------------------------------------------------------
8  *
9  * HD44780 LCD display driver
10  *
11  * The LCD controller is used in 4-bit mode with a full bi-directional
12  * interface (i.e. R/~W is connected) so the busy flag can be read.
13  *
14  * $Id: hd44780.c 2002 2009-06-25 20:21:16Z joerg_wunsch $
15  */
16
17 #include "avr_hd44780_conf.h"
18
19 #include <stdbool.h>
20 #include <stdint.h>
21
22 #include <avr/io.h>
23 #include <util/delay.h>
24
25 #include "avr_hd44780.h"
26
27 #define GLUE(a, b)     a##b
28
29 /* single-bit macros, used for control bits */
30 #define SET_(what, p, m) GLUE(what, p) |= (1 << (m))
31 #define CLR_(what, p, m) GLUE(what, p) &= ~(1 << (m))
32 #define GET_(/* PIN, */ p, m) GLUE(PIN, p) & (1 << (m))
33 #define SET(what, x) SET_(what, x)
34 #define CLR(what, x) CLR_(what, x)
35 #define GET(/* PIN, */ x) GET_(x)
36
37 /* nibble macros, used for data path */
38 #define ASSIGN_(what, p, m, v) GLUE(what, p) = (GLUE(what, p) & \
39                                                 ~((1 << (m)) | (1 << ((m) + 1)) | \
40                                                   (1 << ((m) + 2)) | (1 << ((m) + 3)))) | \
41                                                 ((v) << (m))
42 #define READ_(what, p, m) (GLUE(what, p) & ((1 << (m)) | (1 << ((m) + 1)) | \
43                                             (1 << ((m) + 2)) | (1 << ((m) + 3)))) >> (m)
44 #define ASSIGN(what, x, v) ASSIGN_(what, x, v)
45 #define READ(what, x) READ_(what, x)
46
47 #define HD44780_BUSYFLAG 0x80
48
49 /*
50  * Send one pulse to the E signal (enable).  Mind the timing
51  * constraints.  If readback is set to true, read the HD44780 data
52  * pins right before the falling edge of E, and return that value.
53  */
54 static inline uint8_t
55 hd44780_pulse_e(bool readback) __attribute__((always_inline));
56
57 static inline uint8_t
58 hd44780_pulse_e(bool readback)
59 {
60   uint8_t x;
61
62   SET(PORT, HD44780_E);
63   /*
64    * Guarantee at least 500 ns of pulse width.  For high CPU
65    * frequencies, a delay loop is used.  For lower frequencies, NOPs
66    * are used, and at or below 1 MHz, the native pulse width will
67    * already be 1 us or more so no additional delays are needed.
68    */
69 #if F_CPU > 4000000UL
70   _delay_us(0.5);
71 #else
72   /*
73    * When reading back, we need one additional NOP, as the value read
74    * back from the input pin is sampled close to the beginning of a
75    * CPU clock cycle, while the previous edge on the output pin is
76    * generated towards the end of a CPU clock cycle.
77    */
78   if (readback)
79     __asm__ volatile("nop");
80 #  if F_CPU > 1000000UL
81   __asm__ volatile("nop");
82 #    if F_CPU > 2000000UL
83   __asm__ volatile("nop");
84   __asm__ volatile("nop");
85 #    endif /* F_CPU > 2000000UL */
86 #  endif /* F_CPU > 1000000UL */
87 #endif
88   if (readback)
89     x = READ(PIN, HD44780_D4);
90   else
91     x = 0;
92   CLR(PORT, HD44780_E);
93
94   return x;
95 }
96
97 /*
98  * Send one nibble out to the LCD controller.
99  */
100 static void
101 hd44780_outnibble(uint8_t n, uint8_t rs)
102 {
103   CLR(PORT, HD44780_RW);
104   if (rs)
105     SET(PORT, HD44780_RS);
106   else
107     CLR(PORT, HD44780_RS);
108   ASSIGN(PORT, HD44780_D4, n);
109   (void)hd44780_pulse_e(false);
110 }
111
112 /*
113  * Send one byte to the LCD controller.  As we are in 4-bit mode, we
114  * have to send two nibbles.
115  */
116 void
117 hd44780_outbyte(uint8_t b, uint8_t rs)
118 {
119   hd44780_outnibble(b >> 4, rs);
120   hd44780_outnibble(b & 0xf, rs);
121 }
122
123 /*
124  * Read one nibble from the LCD controller.
125  */
126 static uint8_t
127 hd44780_innibble(uint8_t rs)
128 {
129   uint8_t x;
130
131   SET(PORT, HD44780_RW);
132   ASSIGN(DDR, HD44780_D4, 0x00);
133   if (rs)
134     SET(PORT, HD44780_RS);
135   else
136     CLR(PORT, HD44780_RS);
137   x = hd44780_pulse_e(true);
138   ASSIGN(DDR, HD44780_D4, 0x0F);
139   CLR(PORT, HD44780_RW);
140
141   return x;
142 }
143
144 /*
145  * Read one byte (i.e. two nibbles) from the LCD controller.
146  */
147 uint8_t
148 hd44780_inbyte(uint8_t rs)
149 {
150   uint8_t x;
151
152   x = hd44780_innibble(rs) << 4;
153   x |= hd44780_innibble(rs);
154
155   return x;
156 }
157
158 /*
159  * Wait until the busy flag is cleared.
160  */
161 void
162 hd44780_wait_ready(bool longwait)
163 {
164 #if USE_BUSY_BIT
165   while (hd44780_incmd() & HD44780_BUSYFLAG) ;
166 #else
167   if (longwait)
168     _delay_ms(1.52);
169   else
170     _delay_us(37);
171 #endif
172 }
173
174 /*
175  * Initialize the LCD controller.
176  *
177  * The initialization sequence has a mandatory timing so the
178  * controller can safely recognize the type of interface desired.
179  * This is the only area where timed waits are really needed as
180  * the busy flag cannot be probed initially.
181  */
182 void
183 hd44780_init(void)
184 {
185   SET(DDR, HD44780_RS);
186   SET(DDR, HD44780_RW);
187   SET(DDR, HD44780_E);
188   ASSIGN(DDR, HD44780_D4, 0x0F);
189
190   _delay_ms(15);                /* 40 ms needed for Vcc = 2.7 V */
191   hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
192   _delay_ms(4.1);
193   hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
194   _delay_ms(0.1);
195   hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
196   _delay_us(40); /* 37 is too short */
197
198   hd44780_outnibble(HD44780_FNSET(0, 1, 0) >> 4, 0);
199   hd44780_wait_ready(false);
200   hd44780_outcmd(HD44780_FNSET(0, 1, 0));
201   hd44780_wait_ready(false);
202   hd44780_outcmd(HD44780_DISPCTL(0, 0, 0));
203   hd44780_wait_ready(false);
204 }
205
206 /*
207  * Prepare the LCD controller pins for powerdown.
208  */
209 void
210 hd44780_powerdown(void)
211 {
212   ASSIGN(PORT, HD44780_D4, 0);
213   CLR(PORT, HD44780_RS);
214   CLR(PORT, HD44780_RW);
215   CLR(PORT, HD44780_E);
216 }