Change to target Digispark board
[riso-kagaku-clone.git] / main.c
1 /*
2  * Basic firmware for an RGB LED notifier clone of the Riso Kaguku
3  * Webmail Notifier.
4  *
5  * Copyright 2016 Jonathan McDowell <noodles@earth.li>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 #include <avr/eeprom.h>
21 #include <avr/interrupt.h>
22 #include <avr/io.h>
23 #include <avr/wdt.h>
24 #include <util/delay.h>
25
26 #include <avr/pgmspace.h>
27 #include "usbdrv.h"
28 #include "libs-device/osccal.h"
29
30 #define GREEN_BIT  1 /* Bit 0 on port B */
31 #define RED_BIT    2 /* Bit 1 on port B */
32 #define BLUE_BIT   4 /* Bit 2 on port B */
33 #define ALL_BITS (RED_BIT | GREEN_BIT | BLUE_BIT)
34 #define CMD_SET_SERIAL 0xfa
35
36 uchar serno_read = 0;
37 int serno_str[] = {
38         USB_STRING_DESCRIPTOR_HEADER(8),
39         'U', 'N', 'S', 'E', 'T', 'X', 'X', 'X',
40 };
41
42 PROGMEM const char usbDescriptorConfiguration[CONFIG_DESCRIPTOR_SIZE] = {    /* USB configuration descriptor */
43         9,          /* sizeof(usbDescriptorConfiguration): length of descriptor in bytes */
44         USBDESCR_CONFIG,    /* descriptor type */
45         18 + 7 + 7 + 9, 0,
46         /* total length of data returned (including inlined descriptors) */
47         1,          /* number of interfaces in this configuration */
48         1,          /* index of this configuration */
49         0,          /* configuration name string index */
50         (1 << 7) | USBATTR_REMOTEWAKE,      /* attributes */
51         USB_CFG_MAX_BUS_POWER/2,            /* max USB current in 2mA units */
52         /* interface descriptor follows inline: */
53         9,          /* sizeof(usbDescrInterface): length of descriptor in bytes */
54         USBDESCR_INTERFACE, /* descriptor type */
55         0,          /* index of this interface */
56         0,          /* alternate setting for this interface */
57         2,          /* endpoints excl 0: number of endpoint descriptors to follow */
58         USB_CFG_INTERFACE_CLASS,
59         USB_CFG_INTERFACE_SUBCLASS,
60         USB_CFG_INTERFACE_PROTOCOL,
61         0,          /* string index for interface */
62         9,          /* sizeof(usbDescrHID): length of descriptor in bytes */
63         USBDESCR_HID,   /* descriptor type: HID */
64         0x01, 0x01, /* BCD representation of HID version */
65         0x00,       /* target country code */
66         0x01,       /* number of HID Report (or other HID class) Descriptor infos to follow */
67         0x22,       /* descriptor type: report */
68         USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH, 0,  /* total length of report descriptor */
69         /* endpoint descriptor for endpoint 1 */
70         7,          /* sizeof(usbDescrEndpoint) */
71         USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
72         (char)0x81, /* IN endpoint number 1 */
73         0x03,       /* attrib: Interrupt endpoint */
74         8, 0,       /* maximum packet size */
75         USB_CFG_INTR_POLL_INTERVAL, /* in ms */
76         /* endpoint descriptor for endpoint 2 */
77         7,          /* sizeof(usbDescrEndpoint) */
78         USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
79         2,                      /* OUT endpoint number 2 */
80         0x03,       /* attrib: Interrupt endpoint */
81         5, 0,       /* maximum packet size */
82         USB_CFG_INTR_POLL_INTERVAL, /* in ms */
83 };
84
85 PROGMEM const char usbHidReportDescriptor[22] = {
86         0x06, 0x00, 0xff,               /* USAGE PAGE (Generic Desktop) */
87         0x09, 0x01,                     /* USAGE (Vendor Usage 1) */
88         0xa1, 0x01,                     /* COLLECTION (Application) */
89         0x15, 0x00,                     /*   LOGICAL_MINIMUM (0) */
90         0x26, 0xff, 0x00,               /*   LOGICAL_MAXIMUM (255) */
91         0x75, 0x08,                     /*   REPORT_SIZE (8 bits) */
92         0x95, 0x06,                     /*   REPORT_COUNT (6 elements) */
93         0x09, 0x00,                     /*   USAGE (Undefined) */
94         0xb2, 0x02, 0x01,               /*   FEATURE (Data, Var, Abs, Buf) */
95         0xc0                            /* END_COLLECTION */
96 };
97
98 inline char hexdigit(int i)
99 {
100         return (i < 10) ? ('0' + i) : ('A' - 10 + i);
101 }
102
103 void fetch_serno(void)
104 {
105         uint32_t serno;
106
107         if (!serno_read) {
108                 eeprom_read_block(&serno, 0, 4);
109                 if (serno == 0xffffffff) {
110                         serno_str[1] = 'U';
111                         serno_str[2] = 'N';
112                         serno_str[3] = 'S';
113                         serno_str[4] = 'E';
114                         serno_str[5] = 'T';
115                         serno_str[6] = 'X';
116                         serno_str[7] = 'X';
117                         serno_str[8] = 'X';
118                 } else {
119                         serno_str[1] = hexdigit((serno >> 28));
120                         serno_str[2] = hexdigit((serno >> 24) & 0xF);
121                         serno_str[3] = hexdigit((serno >> 20) & 0xF);
122                         serno_str[4] = hexdigit((serno >> 16) & 0xF);
123                         serno_str[5] = hexdigit((serno >> 12) & 0xF);
124                         serno_str[6] = hexdigit((serno >>  8) & 0xF);
125                         serno_str[7] = hexdigit((serno >>  4) & 0xF);
126                         serno_str[8] = hexdigit( serno        & 0xF);
127                 }
128                 serno_read = 1;
129         }
130 }
131
132 void update_serno(uint32_t serno)
133 {
134         eeprom_write_block(&serno, 0x00, 4);
135
136         serno_str[1] = hexdigit((serno >> 28));
137         serno_str[2] = hexdigit((serno >> 24) & 0xF);
138         serno_str[3] = hexdigit((serno >> 20) & 0xF);
139         serno_str[4] = hexdigit((serno >> 16) & 0xF);
140         serno_str[5] = hexdigit((serno >> 12) & 0xF);
141         serno_str[6] = hexdigit((serno >>  8) & 0xF);
142         serno_str[7] = hexdigit((serno >>  4) & 0xF);
143         serno_str[8] = hexdigit( serno        & 0xF);
144 }
145
146 usbMsgLen_t usbFunctionSetup(uchar data[8])
147 {
148         usbRequest_t *rq = (void *) data;
149
150         if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
151                 if ((rq->bRequest == USBRQ_HID_GET_REPORT) ||
152                                 (rq->bRequest == USBRQ_HID_SET_REPORT)) {
153                         return 0xFF;
154                 }
155         }
156
157         return 0;
158 }
159
160 usbMsgLen_t usbFunctionDescriptor(usbRequest_t *rq)
161 {
162         if (rq->wValue.bytes[1] == USBDESCR_STRING &&
163                         rq->wValue.bytes[0] == 3) {
164                 usbMsgPtr = (usbMsgPtr_t) serno_str;
165                 return sizeof(serno_str);
166         }
167
168         return 0;
169 }
170
171 uchar usbFunctionRead(uchar *data, uchar len)
172 {
173         uint8_t colour_map[8] = { 0, 2, 1, 5, 3, 6, 4, 7 };
174         uint8_t status;
175
176         if (len != 0) {
177                 status = 0;
178                 if (PORTB & RED_BIT)
179                         status |= 1;
180                 if (PORTB & GREEN_BIT)
181                         status |= 2;
182                 if (PORTB & BLUE_BIT)
183                         status |= 4;
184                 /* Map RGB back to Riso Kagaku colour code. */
185                 data[0] = colour_map[status];
186                 return len;
187         }
188
189         return 0;
190 }
191
192 uchar usbFunctionWrite(uchar *data, uchar len)
193 {
194         /*
195          * The Riso Kagaku has an internal colour table, map it back to an
196          * RGB bitmap.
197          */
198         uint8_t colour_map[8] = { 0, 2, 1, 4, 6, 3, 5, 7 };
199         uint8_t rgb_val, port_val;
200
201         if (data[0] < 8) {
202                 rgb_val = colour_map[data[0]];
203
204                 port_val = PORTB & ~ALL_BITS;
205
206                 if (rgb_val & 1)
207                         port_val |= RED_BIT;
208                 if (rgb_val & 2)
209                         port_val |= GREEN_BIT;
210                 if (rgb_val & 4)
211                         port_val |= BLUE_BIT;
212
213                 PORTB = port_val;
214         } else if (data[0] == CMD_SET_SERIAL) {
215                 update_serno(*(uint32_t *) &data[1]);
216         }
217
218         return len;
219 }
220
221 void usbFunctionWriteOut(uchar *data, uchar len)
222 {
223         /*
224          * The Riso Kagaku has an internal colour table, map it back to an
225          * RGB bitmap.
226          */
227         uint8_t colour_map[8] = { 0, 2, 1, 4, 6, 3, 5, 7 };
228         uint8_t rgb_val, port_val;
229
230         if (!len)
231                 return;
232
233         if (data[0] < 8) {
234                 rgb_val = colour_map[data[0]];
235
236                 port_val = PORTB & ~ALL_BITS;
237
238                 if (rgb_val & 1)
239                         port_val |= RED_BIT;
240                 if (rgb_val & 2)
241                         port_val |= GREEN_BIT;
242                 if (rgb_val & 4)
243                         port_val |= BLUE_BIT;
244
245                 PORTB = port_val;
246         } else if (len == 5 && data[0] == CMD_SET_SERIAL) {
247                 update_serno(*(uint32_t *) &data[1]);
248         }
249 }
250
251 int __attribute__((noreturn)) main(void)
252 {
253         unsigned char i;
254
255         wdt_enable(WDTO_1S);
256
257         usbInit();
258         usbDeviceDisconnect();
259
260         i = 0;
261         while (--i) {
262                 wdt_reset();
263                 _delay_ms(1);
264         }
265
266         usbDeviceConnect();
267
268         /* Set the LED bits to output mode */
269         DDRB |= ALL_BITS;
270         /* Turn them off */
271         PORTB &= ~ALL_BITS;
272
273         sei(); /* We're ready to go; enable interrupts */
274
275         while (1) {
276                 wdt_reset();
277                 usbPoll();
278         }
279 }