Fix serial number retrieval from EEPROM
[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 int serno_str[] = {
37         USB_STRING_DESCRIPTOR_HEADER(8),
38         'U', 'N', 'S', 'E', 'T', 'X', 'X', 'X',
39 };
40
41 PROGMEM const char usbDescriptorConfiguration[CONFIG_DESCRIPTOR_SIZE] = {    /* USB configuration descriptor */
42         9,          /* sizeof(usbDescriptorConfiguration): length of descriptor in bytes */
43         USBDESCR_CONFIG,    /* descriptor type */
44         18 + 7 + 7 + 9, 0,
45         /* total length of data returned (including inlined descriptors) */
46         1,          /* number of interfaces in this configuration */
47         1,          /* index of this configuration */
48         0,          /* configuration name string index */
49         (1 << 7) | USBATTR_REMOTEWAKE,      /* attributes */
50         USB_CFG_MAX_BUS_POWER/2,            /* max USB current in 2mA units */
51         /* interface descriptor follows inline: */
52         9,          /* sizeof(usbDescrInterface): length of descriptor in bytes */
53         USBDESCR_INTERFACE, /* descriptor type */
54         0,          /* index of this interface */
55         0,          /* alternate setting for this interface */
56         2,          /* endpoints excl 0: number of endpoint descriptors to follow */
57         USB_CFG_INTERFACE_CLASS,
58         USB_CFG_INTERFACE_SUBCLASS,
59         USB_CFG_INTERFACE_PROTOCOL,
60         0,          /* string index for interface */
61         9,          /* sizeof(usbDescrHID): length of descriptor in bytes */
62         USBDESCR_HID,   /* descriptor type: HID */
63         0x01, 0x01, /* BCD representation of HID version */
64         0x00,       /* target country code */
65         0x01,       /* number of HID Report (or other HID class) Descriptor infos to follow */
66         0x22,       /* descriptor type: report */
67         USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH, 0,  /* total length of report descriptor */
68         /* endpoint descriptor for endpoint 1 */
69         7,          /* sizeof(usbDescrEndpoint) */
70         USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
71         (char)0x81, /* IN endpoint number 1 */
72         0x03,       /* attrib: Interrupt endpoint */
73         8, 0,       /* maximum packet size */
74         USB_CFG_INTR_POLL_INTERVAL, /* in ms */
75         /* endpoint descriptor for endpoint 2 */
76         7,          /* sizeof(usbDescrEndpoint) */
77         USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
78         2,                      /* OUT endpoint number 2 */
79         0x03,       /* attrib: Interrupt endpoint */
80         5, 0,       /* maximum packet size */
81         USB_CFG_INTR_POLL_INTERVAL, /* in ms */
82 };
83
84 PROGMEM const char usbHidReportDescriptor[22] = {
85         0x06, 0x00, 0xff,               /* USAGE PAGE (Generic Desktop) */
86         0x09, 0x01,                     /* USAGE (Vendor Usage 1) */
87         0xa1, 0x01,                     /* COLLECTION (Application) */
88         0x15, 0x00,                     /*   LOGICAL_MINIMUM (0) */
89         0x26, 0xff, 0x00,               /*   LOGICAL_MAXIMUM (255) */
90         0x75, 0x08,                     /*   REPORT_SIZE (8 bits) */
91         0x95, 0x08,                     /*   REPORT_COUNT (8 elements) */
92         0x09, 0x00,                     /*   USAGE (Undefined) */
93         0xb2, 0x02, 0x01,               /*   FEATURE (Data, Var, Abs, Buf) */
94         0xc0                            /* END_COLLECTION */
95 };
96
97 inline char hexdigit(int i)
98 {
99         return (i < 10) ? ('0' + i) : ('A' - 10 + i);
100 }
101
102 void fetch_serno(void)
103 {
104         uint32_t serno;
105
106         eeprom_read_block(&serno, 0, 4);
107         if (serno == 0xffffffff) {
108                 serno_str[1] = 'U';
109                 serno_str[2] = 'N';
110                 serno_str[3] = 'S';
111                 serno_str[4] = 'E';
112                 serno_str[5] = 'T';
113                 serno_str[6] = 'X';
114                 serno_str[7] = 'X';
115                 serno_str[8] = 'X';
116         } else {
117                 serno_str[1] = hexdigit((serno >> 28));
118                 serno_str[2] = hexdigit((serno >> 24) & 0xF);
119                 serno_str[3] = hexdigit((serno >> 20) & 0xF);
120                 serno_str[4] = hexdigit((serno >> 16) & 0xF);
121                 serno_str[5] = hexdigit((serno >> 12) & 0xF);
122                 serno_str[6] = hexdigit((serno >>  8) & 0xF);
123                 serno_str[7] = hexdigit((serno >>  4) & 0xF);
124                 serno_str[8] = hexdigit( serno        & 0xF);
125         }
126 }
127
128 void update_serno(uint32_t serno)
129 {
130         eeprom_write_block(&serno, 0x00, 4);
131
132         serno_str[1] = hexdigit((serno >> 28));
133         serno_str[2] = hexdigit((serno >> 24) & 0xF);
134         serno_str[3] = hexdigit((serno >> 20) & 0xF);
135         serno_str[4] = hexdigit((serno >> 16) & 0xF);
136         serno_str[5] = hexdigit((serno >> 12) & 0xF);
137         serno_str[6] = hexdigit((serno >>  8) & 0xF);
138         serno_str[7] = hexdigit((serno >>  4) & 0xF);
139         serno_str[8] = hexdigit( serno        & 0xF);
140 }
141
142 usbMsgLen_t usbFunctionSetup(uchar data[8])
143 {
144         usbRequest_t *rq = (void *) data;
145
146         if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
147                 if ((rq->bRequest == USBRQ_HID_GET_REPORT) ||
148                                 (rq->bRequest == USBRQ_HID_SET_REPORT)) {
149                         return 0xFF;
150                 }
151         }
152
153         return 0;
154 }
155
156 usbMsgLen_t usbFunctionDescriptor(usbRequest_t *rq)
157 {
158         if (rq->wValue.bytes[1] == USBDESCR_STRING &&
159                         rq->wValue.bytes[0] == 3) {
160                 usbMsgPtr = (usbMsgPtr_t) serno_str;
161                 return sizeof(serno_str);
162         }
163
164         return 0;
165 }
166
167 uchar usbFunctionRead(uchar *data, uchar len)
168 {
169         uint8_t colour_map[8] = { 0, 2, 1, 5, 3, 6, 4, 7 };
170         uint8_t status;
171
172         if (len != 0) {
173                 status = 0;
174                 if (PORTB & RED_BIT)
175                         status |= 1;
176                 if (PORTB & GREEN_BIT)
177                         status |= 2;
178                 if (PORTB & BLUE_BIT)
179                         status |= 4;
180                 /* Map RGB back to Riso Kagaku colour code. */
181                 data[0] = colour_map[status];
182                 return len;
183         }
184
185         return 0;
186 }
187
188 uchar usbFunctionWrite(uchar *data, uchar len)
189 {
190         /*
191          * The Riso Kagaku has an internal colour table, map it back to an
192          * RGB bitmap.
193          */
194         uint8_t colour_map[8] = { 0, 2, 1, 4, 6, 3, 5, 7 };
195         uint8_t rgb_val, port_val;
196
197         if (data[0] < 8) {
198                 rgb_val = colour_map[data[0]];
199
200                 port_val = PORTB & ~ALL_BITS;
201
202                 if (rgb_val & 1)
203                         port_val |= RED_BIT;
204                 if (rgb_val & 2)
205                         port_val |= GREEN_BIT;
206                 if (rgb_val & 4)
207                         port_val |= BLUE_BIT;
208
209                 PORTB = port_val;
210         } else if (data[0] == CMD_SET_SERIAL) {
211                 update_serno(*(uint32_t *) &data[1]);
212         }
213
214         return len;
215 }
216
217 void usbFunctionWriteOut(uchar *data, uchar len)
218 {
219         /*
220          * The Riso Kagaku has an internal colour table, map it back to an
221          * RGB bitmap.
222          */
223         uint8_t colour_map[8] = { 0, 2, 1, 4, 6, 3, 5, 7 };
224         uint8_t rgb_val, port_val;
225
226         if (!len)
227                 return;
228
229         if (data[0] < 8) {
230                 rgb_val = colour_map[data[0]];
231
232                 port_val = PORTB & ~ALL_BITS;
233
234                 if (rgb_val & 1)
235                         port_val |= RED_BIT;
236                 if (rgb_val & 2)
237                         port_val |= GREEN_BIT;
238                 if (rgb_val & 4)
239                         port_val |= BLUE_BIT;
240
241                 PORTB = port_val;
242         } else if (len == 5 && data[0] == CMD_SET_SERIAL) {
243                 update_serno(*(uint32_t *) &data[1]);
244         }
245 }
246
247 int __attribute__((noreturn)) main(void)
248 {
249         unsigned char i;
250
251         wdt_enable(WDTO_1S);
252
253         fetch_serno();
254
255         usbInit();
256         usbDeviceDisconnect();
257
258         i = 0;
259         while (--i) {
260                 wdt_reset();
261                 _delay_ms(1);
262         }
263
264         usbDeviceConnect();
265
266         /* Set the LED bits to output mode */
267         DDRB |= ALL_BITS;
268         /* Turn them off */
269         PORTB &= ~ALL_BITS;
270
271         sei(); /* We're ready to go; enable interrupts */
272
273         while (1) {
274                 wdt_reset();
275                 usbPoll();
276         }
277 }