blob: 0fe5c98be50cb5f5c577702cc44aa39608fafc91 [file] [log] [blame]
Sylvain Munaut70c10f02022-01-12 11:58:34 +01001/*
2 * usb_gps.c
3 *
4 * Copyright (C) 2019-2022 Sylvain Munaut <tnt@246tNt.com>
5 * SPDX-License-Identifier: GPL-3.0-or-later
6 */
7
8#include <stdint.h>
9#include <stdbool.h>
10#include <string.h>
11
12#include <no2usb/usb.h>
13#include <no2usb/usb_hw.h>
14#include <no2usb/usb_priv.h>
15
16#include <no2usb/usb_proto.h>
17#include <no2usb/usb_cdc_proto.h>
18
19#include "console.h"
Sylvain Munautdc1d7412022-08-25 16:56:43 +020020#include "misc.h"
Sylvain Munaut70c10f02022-01-12 11:58:34 +010021#include "usb_desc_ids.h"
22
23
24#define BUF_SIZE_LOG 9
25#define BUF_SIZE (1 << BUF_SIZE_LOG)
26#define BUF_MASK (BUF_SIZE - 1)
27
28static struct {
29 /* State */
30 bool active;
31
32 /* Buffer */
33 struct {
34 unsigned int wr;
35 unsigned int rd;
36 char data[BUF_SIZE] __attribute__((aligned(4)));
37 } buf;
Sylvain Munautdc1d7412022-08-25 16:56:43 +020038
39#ifdef GPS_PPS_ON_CD
40 /* PPS tracking */
41 struct {
42 uint32_t last;
43 bool set;
44 } pps;
45#endif
Sylvain Munaut70c10f02022-01-12 11:58:34 +010046} g_usb_gps;
47
48
49static void
50_usb_gps_set_active(bool active)
51{
52 /* Save new state */
53 g_usb_gps.active = active;
54
55 /* Reset FIFO if disabled */
56 if (!active)
57 g_usb_gps.buf.wr = g_usb_gps.buf.rd = 0;
58}
59
60static int
61_usb_gps_fill_packet(unsigned int dst_ofs)
62{
63 unsigned int len;
64
65 /* Len available, limited to 64 */
66 len = (g_usb_gps.buf.wr - g_usb_gps.buf.rd) & BUF_MASK;
67
68 if (len > 64)
69 len = 64;
70
71 /* Copy block */
72 usb_data_write(dst_ofs, &g_usb_gps.buf.data[g_usb_gps.buf.rd], (len + 3) & ~3);
73
74 /* Increment read pointer */
75 g_usb_gps.buf.rd = (g_usb_gps.buf.rd + len) & BUF_MASK;
76
77 /* If length was not multiple of 4, we emptied the FIFO,
78 * so we reset it to 0 so we're aligned again */
79 if (len & 3)
80 g_usb_gps.buf.wr = g_usb_gps.buf.rd = 0;
81
82 return len;
83}
84
85
86void
87usb_gps_puts(const char *str)
88{
89 unsigned int nxt;
90 char c;
91
92 if (!g_usb_gps.active)
93 return;
94
95 while ((c = *str++) != '\0')
96 {
97 /* Next write pointer pos and full check */
98 nxt = (g_usb_gps.buf.wr + 1) & BUF_MASK;
99 if (nxt == g_usb_gps.buf.rd)
100 /* If overflow, we keep the latest content ... */
101 g_usb_gps.buf.rd = (g_usb_gps.buf.rd + 1) & BUF_MASK;
102
103 /* Write data */
104 g_usb_gps.buf.data[g_usb_gps.buf.wr] = c;
105
106 /* Update write pointer */
107 g_usb_gps.buf.wr = nxt;
108 }
109}
110
111void
112usb_gps_poll(void)
113{
114 volatile struct usb_ep *ep_regs;
115
116 /* OUT EP: We accept data and throw it away */
117 ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_OUT & 0x1f].out;
118 ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(64);
119
120 /* IN EP: Send whatever is queued */
121 ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_OUT & 0x1f].in;
122
123 if ((ep_regs->bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA)
124 {
125 int len = _usb_gps_fill_packet(ep_regs->bd[0].ptr);
126
127 if (len)
128 ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(len);
129 else
130 ep_regs->bd[0].csr = 0;
131 }
Sylvain Munautdc1d7412022-08-25 16:56:43 +0200132
133#ifdef GPS_PPS_ON_CD
134 /* IN EP CTL: Send PPS pulse */
135 ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_CTL & 0x1f].in;
136
137 if ((ep_regs->bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA)
138 {
139 /* Default request */
Sylvain Munauta53f23a2024-03-06 11:20:40 +0100140 /* Put as static to work around gcc aliasing bug ... */
Sylvain Munautb936e3a2024-03-06 20:55:27 +0100141 static struct usb_cdc_notif_serial_state __attribute__((aligned(4))) notif = {
Sylvain Munautdc1d7412022-08-25 16:56:43 +0200142 .hdr = {
143 .bmRequestType = USB_REQ_READ | USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF,
144 .bRequest = USB_NOTIF_CDC_SERIAL_STATE,
145 .wValue = 0,
146 .wIndex = USB_INTF_GPS_CDC_CTL,
147 .wLength = 2
148 },
149 .bits = 0x00
150 };
Sylvain Munaut1f29c0f2024-03-06 19:57:19 +0100151 const int notif_len = sizeof(struct usb_cdc_notif_serial_state);
Sylvain Munautdc1d7412022-08-25 16:56:43 +0200152
153 /* Check if PPS occurred */
154 uint32_t pps_now = time_pps_read();
155
156 if (pps_now != g_usb_gps.pps.last)
157 {
158 /* Update last */
159 g_usb_gps.pps.last = pps_now;
160
161 /* Queue CD Set */
162 notif.bits = 1;
Sylvain Munaut1f29c0f2024-03-06 19:57:19 +0100163 usb_data_write(ep_regs->bd[0].ptr, &notif, notif_len);
164 ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(notif_len);
Sylvain Munautdc1d7412022-08-25 16:56:43 +0200165
166 /* Need to clear in the future */
167 g_usb_gps.pps.set = true;
168 }
169 else if (g_usb_gps.pps.set)
170 {
171 /* Queue CD Clear */
172 notif.bits = 0;
Sylvain Munaut1f29c0f2024-03-06 19:57:19 +0100173 usb_data_write(ep_regs->bd[0].ptr, &notif, notif_len);
174 ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(notif_len);
Sylvain Munautdc1d7412022-08-25 16:56:43 +0200175
176 /* Cleared */
177 g_usb_gps.pps.set = false;
178 }
179 }
180#endif
Sylvain Munaut70c10f02022-01-12 11:58:34 +0100181}
182
183
184static enum usb_fnd_resp
185_cdc_set_conf(const struct usb_conf_desc *conf)
186{
187 const struct usb_intf_desc *intf;
188
189 if (!conf)
190 return USB_FND_SUCCESS;
191
192 /* Configure control interface */
193 intf = usb_desc_find_intf(conf, USB_INTF_GPS_CDC_CTL, 0, NULL);
194 if (!intf)
195 return USB_FND_ERROR;
196
197 usb_ep_boot(intf, USB_EP_GPS_CDC_CTL, false);
198
199 /* Configure data interface */
200 intf = usb_desc_find_intf(conf, USB_INTF_GPS_CDC_DATA, 0, NULL);
201 if (!intf)
202 return USB_FND_ERROR;
203
204 usb_ep_boot(intf, USB_EP_GPS_CDC_OUT, false);
205 usb_ep_boot(intf, USB_EP_GPS_CDC_IN, false);
206
207 return USB_FND_SUCCESS;
208}
209
210static enum usb_fnd_resp
211_cdc_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
212{
213 static const struct usb_cdc_line_coding linecoding = {
214 .dwDTERate = 115200,
215 .bCharFormat = 2,
216 .bParityType = 0,
217 .bDataBits = 8,
218 };
219
220 /* Check this is handled here */
221 if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF))
222 return USB_FND_CONTINUE;
223
224 if (req->wIndex != USB_INTF_GPS_CDC_CTL)
225 return USB_FND_CONTINUE;
226
227 /* Process request */
228 switch (req->bRequest) {
229 case USB_REQ_CDC_SEND_ENCAPSULATED_COMMAND:
230 /* We don't support any, so just accept and don't care */
231 return USB_FND_SUCCESS;
232
233 case USB_REQ_CDC_GET_ENCAPSULATED_RESPONSE:
234 /* Never anything to return */
235 xfer->len = 0;
236 return USB_FND_SUCCESS;
237
238 case USB_REQ_CDC_SET_LINE_CODING:
239 /* We only support 1 config, doesn't matter what the hosts sends */
240 return USB_FND_SUCCESS;
241
242 case USB_REQ_CDC_GET_LINE_CODING:
243 /* We only support 1 config, send that back */
244 xfer->data = (void*)&linecoding;
245 xfer->len = sizeof(linecoding);
246 return USB_FND_ERROR;
247
248 case USB_REQ_CDC_SET_CONTROL_LINE_STATE:
249 /* Enable if DTR is set */
250 _usb_gps_set_active((req->wValue & 1) != 0);
251 return USB_FND_SUCCESS;
252 }
253
254 /* Anything else is not handled */
255 return USB_FND_ERROR;
256}
257
258
259static struct usb_fn_drv _cdc_drv = {
260 .set_conf = _cdc_set_conf,
261 .ctrl_req = _cdc_ctrl_req,
262};
263
264void
265usb_gps_init(void)
266{
267 memset(&g_usb_gps, 0x00, sizeof(g_usb_gps));
268 usb_register_function_driver(&_cdc_drv);
269}