altos: Add power management to TRNG driver
[fw/altos] / src / drivers / ao_trng_send.c
1 /*
2  * Copyright © 2015 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <ao.h>
19 #include <ao_adc_fast.h>
20 #include <ao_crc.h>
21 #include <ao_trng_send.h>
22 #include <ao_exti.h>
23 #include <ao_power.h>
24
25 static uint8_t  trng_running;
26
27 static void
28 ao_trng_send_raw(uint16_t *buf)
29 {
30         uint16_t        i;
31         uint16_t        t;
32         uint16_t        *rnd = (uint16_t *) ao_adc_ring;
33
34         t = ao_adc_get(AO_USB_IN_SIZE>>1);      /* one 16-bit value per two output bytes */
35         for (i = 0; i < AO_USB_IN_SIZE / sizeof (uint16_t); i++) {
36                 *buf++ = rnd[t];
37                 t = (t + 1) & (AO_ADC_RING_SIZE - 1);
38         }
39 }
40
41 static void
42 ao_trng_send_cooked(uint16_t *buf)
43 {
44         uint16_t        i;
45         uint16_t        t;
46         uint32_t        *rnd = (uint32_t *) ao_adc_ring;
47
48         t = ao_adc_get(AO_USB_IN_SIZE) >> 1;    /* one 16-bit value per output byte */
49         for (i = 0; i < AO_USB_IN_SIZE / sizeof (uint16_t); i++) {
50                 *buf++ = ao_crc_in_32_out_16(rnd[t]);
51                 t = (t + 1) & ((AO_ADC_RING_SIZE>>1) - 1);
52         }
53 }
54
55 static inline int
56 ao_send_raw(void)
57 {
58         return !ao_gpio_get(AO_RAW_PORT, AO_RAW_BIT, AO_RAW_PIN);
59 }
60
61 static void
62 ao_trng_send(void)
63 {
64         static uint16_t *buffer[2];
65         int     usb_buf_id;
66
67         if (!buffer[0]) {
68                 buffer[0] = ao_usb_alloc();
69                 buffer[1] = ao_usb_alloc();
70                 if (!buffer[0])
71                         return;
72         }
73
74         usb_buf_id = 0;
75
76         ao_crc_reset();
77
78         for (;;) {
79                 if (!trng_running) {
80                         /* Delay long enough for the HV power supply
81                          * to stabilize so that the first bits we read
82                          * aren't of poor quality
83                          */
84                         ao_delay(AO_MS_TO_TICKS(10));
85                         trng_running = TRUE;
86                 }
87                 if (ao_send_raw()) {
88                         ao_led_on(AO_LED_TRNG_RAW);
89                         ao_trng_send_raw(buffer[usb_buf_id]);
90                         ao_led_off(AO_LED_TRNG_RAW);
91                 } else {
92                         ao_led_on(AO_LED_TRNG_COOKED);
93                         ao_trng_send_cooked(buffer[usb_buf_id]);
94                         ao_led_off(AO_LED_TRNG_COOKED);
95                 }
96                 ao_adc_ack(AO_USB_IN_SIZE);
97                 ao_usb_write(buffer[usb_buf_id], AO_USB_IN_SIZE);
98                 usb_buf_id = 1-usb_buf_id;
99         }
100 }
101
102 static struct ao_task ao_trng_send_task;
103
104 #if AO_POWER_MANAGEMENT
105
106 static void ao_trng_suspend(void *arg)
107 {
108         (void) arg;
109 #ifdef AO_TRNG_ENABLE_PORT
110         ao_gpio_set(AO_TRNG_ENABLE_PORT, AO_TRNG_ENABLE_BIT, AO_TRNG_ENABLE_PIN, 0);
111 #endif
112         trng_running = FALSE;
113 }
114
115 static void ao_trng_resume(void *arg)
116 {
117         (void) arg;
118 #ifdef AO_TRNG_ENABLE_PORT
119         ao_gpio_set(AO_TRNG_ENABLE_PORT, AO_TRNG_ENABLE_BIT, AO_TRNG_ENABLE_PIN, 1);
120 #endif
121 }
122
123 static struct ao_power ao_trng_power = {
124         .suspend = ao_trng_suspend,
125         .resume = ao_trng_resume
126 };
127
128 #endif
129
130 void
131 ao_trng_send_init(void)
132 {
133 #ifdef AO_TRNG_ENABLE_PORT
134         ao_enable_output(AO_TRNG_ENABLE_PORT, AO_TRNG_ENABLE_BIT, AO_TRNG_ENABLE_PIN, 1);
135         ao_power_register(&ao_trng_power);
136 #endif
137         ao_enable_input(AO_RAW_PORT, AO_RAW_BIT, AO_EXTI_MODE_PULL_UP);
138         ao_add_task(&ao_trng_send_task, ao_trng_send, "trng_send");
139 }