1/* $NetBSD: auvitek_dtv.c,v 1.7 2016/04/23 10:15:31 skrll Exp $ */
2
3/*-
4 * Copyright (c) 2011 Jared D. McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * Auvitek AU0828 USB controller (Digital TV function)
31 */
32
33#include <sys/cdefs.h>
34__KERNEL_RCSID(0, "$NetBSD: auvitek_dtv.c,v 1.7 2016/04/23 10:15:31 skrll Exp $");
35
36#include <sys/param.h>
37#include <sys/systm.h>
38#include <sys/device.h>
39#include <sys/conf.h>
40#include <sys/kmem.h>
41#include <sys/bus.h>
42
43#include <dev/usb/usb.h>
44#include <dev/usb/usbdi.h>
45#include <dev/usb/usbdivar.h>
46#include <dev/usb/usbdi_util.h>
47#include <dev/usb/usbdevs.h>
48
49#include <dev/dtv/dtvif.h>
50
51#include <dev/usb/auvitekreg.h>
52#include <dev/usb/auvitekvar.h>
53
54static void auvitek_dtv_get_devinfo(void *,
55 struct dvb_frontend_info *);
56static int auvitek_dtv_open(void *, int);
57static void auvitek_dtv_close(void *);
58static int auvitek_dtv_set_tuner(void *,
59 const struct dvb_frontend_parameters *);
60static fe_status_t auvitek_dtv_get_status(void *);
61static uint16_t auvitek_dtv_get_signal_strength(void *);
62static uint16_t auvitek_dtv_get_snr(void *);
63static int auvitek_dtv_start_transfer(void *,
64 void (*)(void *, const struct dtv_payload *),
65 void *);
66static int auvitek_dtv_stop_transfer(void *);
67
68static int auvitek_dtv_init_pipes(struct auvitek_softc *);
69static int auvitek_dtv_abort_pipes(struct auvitek_softc *);
70static int auvitek_dtv_close_pipes(struct auvitek_softc *);
71
72static int auvitek_dtv_bulk_start(struct auvitek_softc *);
73static int auvitek_dtv_bulk_start1(struct auvitek_bulk_xfer *);
74static void auvitek_dtv_bulk_cb(struct usbd_xfer *, void *,
75 usbd_status);
76
77static const struct dtv_hw_if auvitek_dtv_if = {
78 .get_devinfo = auvitek_dtv_get_devinfo,
79 .open = auvitek_dtv_open,
80 .close = auvitek_dtv_close,
81 .set_tuner = auvitek_dtv_set_tuner,
82 .get_status = auvitek_dtv_get_status,
83 .get_signal_strength = auvitek_dtv_get_signal_strength,
84 .get_snr = auvitek_dtv_get_snr,
85 .start_transfer = auvitek_dtv_start_transfer,
86 .stop_transfer = auvitek_dtv_stop_transfer,
87};
88
89int
90auvitek_dtv_attach(struct auvitek_softc *sc)
91{
92
93 auvitek_dtv_rescan(sc, NULL, NULL);
94
95 return sc->sc_dtvdev != NULL;
96}
97
98int
99auvitek_dtv_detach(struct auvitek_softc *sc, int flags)
100{
101 if (sc->sc_dtvdev != NULL) {
102 config_detach(sc->sc_dtvdev, flags);
103 sc->sc_dtvdev = NULL;
104 }
105
106 return 0;
107}
108
109void
110auvitek_dtv_rescan(struct auvitek_softc *sc, const char *ifattr,
111 const int *locs)
112{
113 struct dtv_attach_args daa;
114
115 daa.hw = &auvitek_dtv_if;
116 daa.priv = sc;
117
118 if (ifattr_match(ifattr, "dtvbus") && sc->sc_dtvdev == NULL)
119 sc->sc_dtvdev = config_found_ia(sc->sc_dev, "dtvbus",
120 &daa, dtv_print);
121}
122
123void
124auvitek_dtv_childdet(struct auvitek_softc *sc, device_t child)
125{
126 if (sc->sc_dtvdev == child)
127 sc->sc_dtvdev = NULL;
128}
129
130static void
131auvitek_dtv_get_devinfo(void *priv, struct dvb_frontend_info *info)
132{
133 struct auvitek_softc *sc = priv;
134
135 memset(info, 0, sizeof(*info));
136 strlcpy(info->name, sc->sc_descr, sizeof(info->name));
137 info->type = FE_ATSC;
138 info->frequency_min = 54000000;
139 info->frequency_max = 858000000;
140 info->frequency_stepsize = 62500;
141 info->caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB;
142}
143
144static int
145auvitek_dtv_open(void *priv, int flags)
146{
147 struct auvitek_softc *sc = priv;
148
149 if (sc->sc_dying)
150 return EIO;
151
152 auvitek_attach_tuner(sc->sc_dev);
153 if (sc->sc_xc5k == NULL)
154 return ENXIO;
155
156 int err = auvitek_dtv_init_pipes(sc);
157 if (err)
158 return err;
159
160 for (size_t i = 0; i < AUVITEK_NBULK_XFERS; i++) {
161 sc->sc_ab.ab_bx[i].bx_sc = sc;
162 err = usbd_create_xfer(sc->sc_ab.ab_pipe,
163 AUVITEK_BULK_BUFLEN, 0, 0, &sc->sc_ab.ab_bx[i].bx_xfer);
164 if (err) {
165 aprint_error_dev(sc->sc_dev,
166 "couldn't allocate xfer\n");
167 sc->sc_dying = 1;
168 return err;
169 }
170 sc->sc_ab.ab_bx[i].bx_buffer = usbd_get_buffer(
171 sc->sc_ab.ab_bx[i].bx_xfer);
172 }
173
174
175 return 0;
176}
177
178static void
179auvitek_dtv_close(void *priv)
180{
181 struct auvitek_softc *sc = priv;
182
183 auvitek_dtv_stop_transfer(sc);
184 auvitek_dtv_abort_pipes(sc);
185
186 for (size_t i = 0; i < AUVITEK_NBULK_XFERS; i++) {
187 if (sc->sc_ab.ab_bx[i].bx_xfer)
188 usbd_destroy_xfer(sc->sc_ab.ab_bx[i].bx_xfer);
189 }
190
191 auvitek_dtv_close_pipes(sc);
192
193 sc->sc_dtvsubmitcb = NULL;
194 sc->sc_dtvsubmitarg = NULL;
195}
196
197static int
198auvitek_dtv_set_tuner(void *priv, const struct dvb_frontend_parameters *params)
199{
200 struct auvitek_softc *sc = priv;
201 int error;
202
203 error = au8522_set_modulation(sc->sc_au8522, params->u.vsb.modulation);
204 if (error)
205 return error;
206
207 delay(100000);
208
209 au8522_set_gate(sc->sc_au8522, true);
210 error = xc5k_tune_dtv(sc->sc_xc5k, params);
211 au8522_set_gate(sc->sc_au8522, false);
212
213 return error;
214}
215
216fe_status_t
217auvitek_dtv_get_status(void *priv)
218{
219 struct auvitek_softc *sc = priv;
220
221 return au8522_get_dtv_status(sc->sc_au8522);
222}
223
224uint16_t
225auvitek_dtv_get_signal_strength(void *priv)
226{
227 return auvitek_dtv_get_snr(priv);
228}
229
230uint16_t
231auvitek_dtv_get_snr(void *priv)
232{
233 struct auvitek_softc *sc = priv;
234
235 return au8522_get_snr(sc->sc_au8522);
236}
237
238static int
239auvitek_dtv_start_transfer(void *priv,
240 void (*cb)(void *, const struct dtv_payload *), void *arg)
241{
242 struct auvitek_softc *sc = priv;
243 int s;
244
245 if (sc->sc_ab.ab_running) {
246 return 0;
247 }
248
249 sc->sc_dtvsubmitcb = cb;
250 sc->sc_dtvsubmitarg = arg;
251
252 auvitek_write_1(sc, 0x608, 0x90);
253 auvitek_write_1(sc, 0x609, 0x72);
254 auvitek_write_1(sc, 0x60a, 0x71);
255 auvitek_write_1(sc, 0x60b, 0x01);
256
257 sc->sc_ab.ab_running = true;
258
259 s = splusb();
260 auvitek_dtv_bulk_start(sc);
261 splx(s);
262
263 return 0;
264}
265
266static int
267auvitek_dtv_stop_transfer(void *priv)
268{
269 struct auvitek_softc *sc = priv;
270
271 sc->sc_ab.ab_running = false;
272
273 auvitek_write_1(sc, 0x608, 0x00);
274 auvitek_write_1(sc, 0x609, 0x00);
275 auvitek_write_1(sc, 0x60a, 0x00);
276 auvitek_write_1(sc, 0x60b, 0x00);
277
278 return 0;
279}
280
281static int
282auvitek_dtv_init_pipes(struct auvitek_softc *sc)
283{
284 usbd_status err;
285
286 KERNEL_LOCK(1, curlwp);
287 err = usbd_open_pipe(sc->sc_bulk_iface, sc->sc_ab.ab_endpt,
288 USBD_EXCLUSIVE_USE|USBD_MPSAFE, &sc->sc_ab.ab_pipe);
289 KERNEL_UNLOCK_ONE(curlwp);
290
291 if (err) {
292 aprint_error_dev(sc->sc_dev, "couldn't open bulk-in pipe: %s\n",
293 usbd_errstr(err));
294 return ENOMEM;
295 }
296
297 return 0;
298}
299
300static int
301auvitek_dtv_abort_pipes(struct auvitek_softc *sc)
302{
303 if (sc->sc_ab.ab_pipe != NULL) {
304 KERNEL_LOCK(1, curlwp);
305 usbd_abort_pipe(sc->sc_ab.ab_pipe);
306 KERNEL_UNLOCK_ONE(curlwp);
307 }
308
309 return 0;
310}
311
312static int
313auvitek_dtv_close_pipes(struct auvitek_softc *sc)
314{
315 if (sc->sc_ab.ab_pipe != NULL) {
316 KERNEL_LOCK(1, curlwp);
317 usbd_close_pipe(sc->sc_ab.ab_pipe);
318 KERNEL_UNLOCK_ONE(curlwp);
319 sc->sc_ab.ab_pipe = NULL;
320 }
321
322 return 0;
323}
324
325static void
326auvitek_dtv_bulk_cb(struct usbd_xfer *xfer, void *priv,
327 usbd_status status)
328{
329 struct auvitek_bulk_xfer *bx = priv;
330 struct auvitek_softc *sc = bx->bx_sc;
331 struct auvitek_bulk *ab = &sc->sc_ab;
332 struct dtv_payload payload;
333 uint32_t xferlen;
334
335 if (ab->ab_running == false || sc->sc_dtvsubmitcb == NULL)
336 return;
337
338 usbd_get_xfer_status(xfer, NULL, NULL, &xferlen, NULL);
339
340 //printf("%s: status=%d xferlen=%u\n", __func__, status, xferlen);
341
342 if (status != USBD_NORMAL_COMPLETION) {
343 printf("%s: USB error (%s)\n", __func__, usbd_errstr(status));
344 if (status == USBD_STALLED) {
345 usbd_clear_endpoint_stall_async(ab->ab_pipe);
346 goto next;
347 }
348 if (status == USBD_SHORT_XFER) {
349 goto next;
350 }
351 return;
352 }
353
354 if (xferlen == 0) {
355 printf("%s: 0-length xfer\n", __func__);
356 goto next;
357 }
358
359 payload.data = bx->bx_buffer;
360 payload.size = xferlen;
361 sc->sc_dtvsubmitcb(sc->sc_dtvsubmitarg, &payload);
362
363next:
364 auvitek_dtv_bulk_start1(bx);
365}
366
367static int
368auvitek_dtv_bulk_start(struct auvitek_softc *sc)
369{
370 int i, error;
371
372 for (i = 0; i < AUVITEK_NBULK_XFERS; i++) {
373 error = auvitek_dtv_bulk_start1(&sc->sc_ab.ab_bx[i]);
374 if (error)
375 return error;
376 }
377
378 return 0;
379}
380
381static int
382auvitek_dtv_bulk_start1(struct auvitek_bulk_xfer *bx)
383{
384 struct auvitek_softc *sc = bx->bx_sc;
385 usbd_status err;
386
387 usbd_setup_xfer(bx->bx_xfer, bx, bx->bx_buffer, AUVITEK_BULK_BUFLEN,
388 0 /* USBD_SHORT_XFER_OK */, 100 /* USBD_NO_TIMEOUT */,
389 auvitek_dtv_bulk_cb);
390
391 KERNEL_LOCK(1, curlwp);
392 err = usbd_transfer(bx->bx_xfer);
393 KERNEL_UNLOCK_ONE(curlwp);
394
395 if (err != USBD_IN_PROGRESS) {
396 aprint_error_dev(sc->sc_dev, "USB error: %s\n",
397 usbd_errstr(err));
398 return ENODEV;
399 }
400
401 return 0;
402}
403