Version 0.10
[gnuk/gnuk.git] / src / pin-dial.c
1 /*
2  * pin-dial.c -- PIN input device support (rotary encoder + push switch)
3  *
4  * Copyright (C) 2011 Free Software Initiative of Japan
5  * Author: NIIBE Yutaka <gniibe@fsij.org>
6  *
7  * This file is a part of Gnuk, a GnuPG USB Token implementation.
8  *
9  * Gnuk is free software: you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Gnuk is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  */
23
24 #include "config.h"
25 #include "ch.h"
26 #include "hal.h"
27 #include "board.h"
28 #include "gnuk.h"
29
30 uint8_t pin_input_buffer[MAX_PIN_CHARS];
31 uint8_t pin_input_len;
32
33 #define LED_DISP_BLINK_INTERVAL0        MS2ST(150)
34 #define LED_DISP_BLINK_INTERVAL1        MS2ST(100)
35
36 /*
37  * PB6 TIM4_CH1
38  * PB7 TIM4_CH2
39  *
40  * TIM4_SMCR
41  *  SMS = 001
42  * TIM4_CCER
43  *  CC1P = 0
44  *  CC2P = 0
45  * TIM4_CCMR1
46  *  CC1S= 01
47  *  CC2S= 01
48  * TIM4_CR1
49  *  CEN= 1
50  */
51
52 #define OFF   '\x00'
53 #define ENTER '\x0a'
54 static struct led_pattern { uint8_t c, v; } led_pattern[] = 
55 {
56                      /* char : dp a b c d e f g */
57   { ENTER, 0xf8 },   /* |-   :  1 1 1 1 1 0 0 0  (enter) */
58   { ' ', 0xff },     /* SPC  :  1 1 1 1 1 1 1 1 */
59   { '0', 0x81 },     /* 0    :  1 0 0 0 0 0 0 1 */
60   { '1', 0xcf },     /* 1    :  1 1 0 0 1 1 1 1 */
61   { '2', 0x92 },     /* 2    :  1 0 0 1 0 0 1 0 */
62   { '3', 0x86 },     /* 3    :  1 0 0 0 0 1 1 0 */
63   { '4', 0xcc },     /* 4    :  1 1 0 0 1 1 0 0 */
64   { '5', 0xa4 },     /* 5    :  1 0 1 0 0 1 0 0 */
65   { '6', 0xa0 },     /* 6    :  1 0 1 0 0 0 0 0 */
66   { '7', 0x8d },     /* 7    :  1 0 0 0 1 1 0 1 */
67   { '8', 0x80 },     /* 8    :  1 0 0 0 0 0 0 0 */
68   { '9', 0x84 },     /* 9    :  1 0 0 0 0 1 0 0 */
69   { 'A', 0x88 },     /* A    :  1 0 0 0 1 0 0 0 */
70   { 'B', 0xe0 },     /* b    :  1 1 1 0 0 0 0 0 */
71   { 'C', 0xb1 },     /* C    :  1 0 1 1 0 0 0 1 */
72   { 'D', 0xc2 },     /* d    :  1 1 0 0 0 0 1 0 */
73   { 'E', 0xb0 },     /* E    :  1 0 1 1 0 0 0 0 */
74   { 'F', 0xb8 },     /* F    :  1 0 1 1 1 0 0 0 */
75
76   { 'G', 0xa1 },     /* G    :  1 0 1 0 0 0 0 1 */
77   { '\xff', 0xce },  /* -|   :  1 1 0 0 1 1 1 0  (backspace) */
78 };
79
80 #define LENGTH_LED_PATTERN (int)(sizeof led_pattern / sizeof (struct led_pattern))
81
82 static void
83 led_disp (uint8_t c)
84 {
85   uint16_t v = palReadPort (IOPORT2) | 0x00ff;
86
87   if (c == OFF)
88     v |= 0xff00;
89   else
90     {
91       int i;
92
93       v &= 0x80ff;
94       for (i = 0; i < LENGTH_LED_PATTERN; i++)
95         if (led_pattern[i].c == c)
96           {
97             v |= ((led_pattern[i].v & 0x7f) << 8); /* Don't touch DP.  */
98             break;
99           }
100         else if (led_pattern[i].c > c)
101           {
102             v |= 0x7f00;                /* default: SPC */
103             break;
104           }
105     }
106
107   palWritePort (IOPORT2, v);
108 }
109
110 static void
111 blink_dp (void)
112 {
113   uint16_t v = palReadPort (IOPORT2) | 0x00ff;
114
115   v ^= 0x8000;
116   palWritePort (IOPORT2, v);
117 }
118
119 static Thread *pin_thread;
120 #define EV_SW_PUSH (eventmask_t)1
121
122 void
123 dial_sw_interrupt (void)
124 {
125   dial_sw_disable ();
126   chEvtSignalI (pin_thread, EV_SW_PUSH);
127   palClearPad (IOPORT1, GPIOA_LED2);
128 }
129
130
131 msg_t
132 pin_main (void *arg)
133 {
134   int msg_code = (int)arg;
135   uint16_t count, count_prev;
136   uint8_t input_mode;
137   uint8_t sw_push_count;
138   uint8_t sw_event;
139
140   (void)msg_code;
141
142   pin_thread = chThdSelf ();
143   led_disp (' ');
144
145   TIM4->CNT = 0;
146   TIM4->CR1 |= TIM_CR1_CEN;
147   input_mode = 0;
148   count = count_prev = 0;
149   pin_input_len = 0;
150   sw_push_count = 0;
151   sw_event = 0;
152
153   while (!chThdShouldTerminate ())
154     {
155       eventmask_t m;
156
157       blink_dp ();
158       dial_sw_enable ();
159       m = chEvtWaitOneTimeout (ALL_EVENTS, LED_DISP_BLINK_INTERVAL0);
160
161       if (m == EV_SW_PUSH || sw_push_count)
162         {
163           if (palReadPad (IOPORT2, GPIOB_BUTTON) == 0)
164             sw_push_count++;
165           else                  /* ignore this bounce */
166             {
167               palSetPad (IOPORT1, GPIOA_LED2);
168               sw_push_count = 0;
169             }
170         }
171
172       if (sw_push_count >= 2)
173         sw_event = 1;
174
175       count = (TIM4->CNT) / 2;
176
177       if (input_mode == 1)
178         {
179           if (count_prev != count)
180             input_mode = 0;
181           else
182             {
183               led_disp (ENTER);
184               if (sw_event)
185                 {
186                   palSetPad (IOPORT1, GPIOA_LED2);
187                   break;
188                 }
189             }
190         }
191
192       if (input_mode == 0)
193         {
194           uint8_t c;
195
196           if (count < 10)
197             c = count + '0';
198           else
199             c = (count - 10) + 'A';
200
201           led_disp (c);
202
203           if (sw_event)
204             {
205               pin_input_buffer[pin_input_len] = c;
206               if (pin_input_len < MAX_PIN_CHARS - 1)
207                 pin_input_len++;
208               input_mode = 1;
209               sw_event = sw_push_count = 0;
210               palSetPad (IOPORT1, GPIOA_LED2);
211             }
212         }
213
214       chThdSleep (LED_DISP_BLINK_INTERVAL1);
215       count_prev = count;
216     }
217
218   led_disp (OFF);
219   TIM4->CR1 &= ~TIM_CR1_CEN;
220   dial_sw_disable ();
221   return 0;
222 }