blob: 1c10033b905e00f5c7dac348e120fe77fabbc38e [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"
20#include "usb_desc_ids.h"
21
22
23#define BUF_SIZE_LOG 9
24#define BUF_SIZE (1 << BUF_SIZE_LOG)
25#define BUF_MASK (BUF_SIZE - 1)
26
27static struct {
28 /* State */
29 bool active;
30
31 /* Buffer */
32 struct {
33 unsigned int wr;
34 unsigned int rd;
35 char data[BUF_SIZE] __attribute__((aligned(4)));
36 } buf;
37} g_usb_gps;
38
39
40static void
41_usb_gps_set_active(bool active)
42{
43 /* Save new state */
44 g_usb_gps.active = active;
45
46 /* Reset FIFO if disabled */
47 if (!active)
48 g_usb_gps.buf.wr = g_usb_gps.buf.rd = 0;
49}
50
51static int
52_usb_gps_fill_packet(unsigned int dst_ofs)
53{
54 unsigned int len;
55
56 /* Len available, limited to 64 */
57 len = (g_usb_gps.buf.wr - g_usb_gps.buf.rd) & BUF_MASK;
58
59 if (len > 64)
60 len = 64;
61
62 /* Copy block */
63 usb_data_write(dst_ofs, &g_usb_gps.buf.data[g_usb_gps.buf.rd], (len + 3) & ~3);
64
65 /* Increment read pointer */
66 g_usb_gps.buf.rd = (g_usb_gps.buf.rd + len) & BUF_MASK;
67
68 /* If length was not multiple of 4, we emptied the FIFO,
69 * so we reset it to 0 so we're aligned again */
70 if (len & 3)
71 g_usb_gps.buf.wr = g_usb_gps.buf.rd = 0;
72
73 return len;
74}
75
76
77void
78usb_gps_puts(const char *str)
79{
80 unsigned int nxt;
81 char c;
82
83 if (!g_usb_gps.active)
84 return;
85
86 while ((c = *str++) != '\0')
87 {
88 /* Next write pointer pos and full check */
89 nxt = (g_usb_gps.buf.wr + 1) & BUF_MASK;
90 if (nxt == g_usb_gps.buf.rd)
91 /* If overflow, we keep the latest content ... */
92 g_usb_gps.buf.rd = (g_usb_gps.buf.rd + 1) & BUF_MASK;
93
94 /* Write data */
95 g_usb_gps.buf.data[g_usb_gps.buf.wr] = c;
96
97 /* Update write pointer */
98 g_usb_gps.buf.wr = nxt;
99 }
100}
101
102void
103usb_gps_poll(void)
104{
105 volatile struct usb_ep *ep_regs;
106
107 /* OUT EP: We accept data and throw it away */
108 ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_OUT & 0x1f].out;
109 ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(64);
110
111 /* IN EP: Send whatever is queued */
112 ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_OUT & 0x1f].in;
113
114 if ((ep_regs->bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA)
115 {
116 int len = _usb_gps_fill_packet(ep_regs->bd[0].ptr);
117
118 if (len)
119 ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(len);
120 else
121 ep_regs->bd[0].csr = 0;
122 }
123}
124
125
126static enum usb_fnd_resp
127_cdc_set_conf(const struct usb_conf_desc *conf)
128{
129 const struct usb_intf_desc *intf;
130
131 if (!conf)
132 return USB_FND_SUCCESS;
133
134 /* Configure control interface */
135 intf = usb_desc_find_intf(conf, USB_INTF_GPS_CDC_CTL, 0, NULL);
136 if (!intf)
137 return USB_FND_ERROR;
138
139 usb_ep_boot(intf, USB_EP_GPS_CDC_CTL, false);
140
141 /* Configure data interface */
142 intf = usb_desc_find_intf(conf, USB_INTF_GPS_CDC_DATA, 0, NULL);
143 if (!intf)
144 return USB_FND_ERROR;
145
146 usb_ep_boot(intf, USB_EP_GPS_CDC_OUT, false);
147 usb_ep_boot(intf, USB_EP_GPS_CDC_IN, false);
148
149 return USB_FND_SUCCESS;
150}
151
152static enum usb_fnd_resp
153_cdc_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
154{
155 static const struct usb_cdc_line_coding linecoding = {
156 .dwDTERate = 115200,
157 .bCharFormat = 2,
158 .bParityType = 0,
159 .bDataBits = 8,
160 };
161
162 /* Check this is handled here */
163 if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF))
164 return USB_FND_CONTINUE;
165
166 if (req->wIndex != USB_INTF_GPS_CDC_CTL)
167 return USB_FND_CONTINUE;
168
169 /* Process request */
170 switch (req->bRequest) {
171 case USB_REQ_CDC_SEND_ENCAPSULATED_COMMAND:
172 /* We don't support any, so just accept and don't care */
173 return USB_FND_SUCCESS;
174
175 case USB_REQ_CDC_GET_ENCAPSULATED_RESPONSE:
176 /* Never anything to return */
177 xfer->len = 0;
178 return USB_FND_SUCCESS;
179
180 case USB_REQ_CDC_SET_LINE_CODING:
181 /* We only support 1 config, doesn't matter what the hosts sends */
182 return USB_FND_SUCCESS;
183
184 case USB_REQ_CDC_GET_LINE_CODING:
185 /* We only support 1 config, send that back */
186 xfer->data = (void*)&linecoding;
187 xfer->len = sizeof(linecoding);
188 return USB_FND_ERROR;
189
190 case USB_REQ_CDC_SET_CONTROL_LINE_STATE:
191 /* Enable if DTR is set */
192 _usb_gps_set_active((req->wValue & 1) != 0);
193 return USB_FND_SUCCESS;
194 }
195
196 /* Anything else is not handled */
197 return USB_FND_ERROR;
198}
199
200
201static struct usb_fn_drv _cdc_drv = {
202 .set_conf = _cdc_set_conf,
203 .ctrl_req = _cdc_ctrl_req,
204};
205
206void
207usb_gps_init(void)
208{
209 memset(&g_usb_gps, 0x00, sizeof(g_usb_gps));
210 usb_register_function_driver(&_cdc_drv);
211}