di.c revision 1.1
1/* $NetBSD: di.c,v 1.1 2026/01/09 22:54:29 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2025 Jared 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 AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: di.c,v 1.1 2026/01/09 22:54:29 jmcneill Exp $");
31
32#include <sys/param.h>
33#include <sys/bus.h>
34#include <sys/device.h>
35#include <sys/systm.h>
36#include <sys/callout.h>
37#include <sys/buf.h>
38#include <sys/dvdio.h>
39
40#include <uvm/uvm_extern.h>
41
42#include <dev/scsipi/scsi_all.h>
43#include <dev/scsipi/scsi_disk.h>
44#include <dev/scsipi/scsipi_all.h>
45#include <dev/scsipi/scsipi_cd.h>
46#include <dev/scsipi/scsipi_disk.h>
47#include <dev/scsipi/scsiconf.h>
48
49#include <machine/wii.h>
50#include <machine/wiiu.h>
51#include <machine/pio.h>
52#include "ahb.h"
53
54#ifdef DI_DEBUG
55#define DPRINTF(dv, fmt, ...)	device_printf(dv, fmt, ## __VA_ARGS__)
56#else
57#define DPRINTF(dv, fmt, ...)
58#endif
59
60#define DI_REG_SIZE	0x40
61
62#define DISR		0x00
63#define  DISR_BRKINT		__BIT(6)
64#define  DISR_BRKINTMASK	__BIT(5)
65#define  DISR_TCINT		__BIT(4)
66#define  DISR_TCINTMASK		__BIT(3)
67#define  DISR_DEINT		__BIT(2)
68#define  DISR_DEINTMASK		__BIT(1)
69#define  DISR_BRK		__BIT(0)
70#define DICVR		0x04
71#define  DICVR_CVRINT		__BIT(2)
72#define  DICVR_CVRINTMASK	__BIT(1)
73#define  DICVR_CVR		__BIT(0)
74#define DICMDBUF0	0x08
75#define DICMDBUF1	0x0c
76#define DICMDBUF2	0x10
77#define DIMAR		0x14
78#define DILENGTH	0x18
79#define DICR		0x1c
80#define  DICR_DMA		__BIT(1)
81#define  DICR_TSTART		__BIT(0)
82#define DIMMBUF		0x20
83#define DICFG		0x24
84
85#define DI_CMD_INQUIRY			0x12000000
86#define DI_CMD_REPORT_KEY(x)		(0xa4000000 | ((uint32_t)(x) << 16))
87#define DI_CMD_READ_DVD_STRUCT(x)	(0xad000000 | ((uint32_t)(x) << 24))
88#define DI_CMD_READ_DVD			0xd0000000
89#define DI_CMD_REQUEST_ERROR		0xe0000000
90#define DI_CMD_STOP_MOTOR		0xe3000000
91
92#define DVDBLOCKSIZE	2048
93
94#define	DI_IDLE_TIMEOUT_MS	30000
95
96struct di_softc;
97
98static int	di_match(device_t, cfdata_t, void *);
99static void	di_attach(device_t, device_t, void *);
100
101static bool	di_shutdown(device_t, int);
102
103static int	di_intr(void *);
104static void	di_timeout(void *);
105static void	di_idle(void *);
106
107static void	di_request(struct scsipi_channel *, scsipi_adapter_req_t,
108			   void *);
109static void	di_init_regs(struct di_softc *);
110static void	di_reset(struct di_softc *, bool);
111
112struct di_response_inquiry {
113	uint16_t		revision_level;
114	uint16_t		device_code;
115	uint32_t		release_date;
116	uint8_t			padding[24];
117} __aligned(4);
118CTASSERT(sizeof(struct di_response_inquiry) == 0x20);
119
120struct di_softc {
121	device_t		sc_dev;
122	bus_space_tag_t		sc_bst;
123	bus_space_handle_t	sc_bsh;
124	bus_dma_tag_t		sc_dmat;
125
126	struct scsipi_adapter	sc_adapter;
127	struct scsipi_channel	sc_channel;
128
129	struct scsipi_xfer	*sc_cur_xs;
130	callout_t		sc_timeout;
131	callout_t		sc_idle;
132	int			sc_pamr;
133
134	bus_dmamap_t		sc_dma_map;
135	void			*sc_dma_addr;
136	size_t			sc_dma_size;
137	bus_dma_segment_t	sc_dma_segs[1];
138};
139
140#define WR4(sc, reg, val)	\
141	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
142#define RD4(sc, reg)		\
143	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
144
145CFATTACH_DECL_NEW(di, sizeof(struct di_softc),
146    di_match, di_attach, NULL, NULL);
147
148static int
149di_match(device_t parent, cfdata_t cf, void *aux)
150{
151	return !wiiu_native;
152}
153
154static void
155di_attach(device_t parent, device_t self, void *aux)
156{
157	struct ahb_attach_args *aaa = aux;
158	struct di_softc *sc = device_private(self);
159	struct scsipi_adapter *adapt = &sc->sc_adapter;
160	struct scsipi_channel *chan = &sc->sc_channel;
161	int error, nsegs;
162
163	sc->sc_dev = self;
164	sc->sc_dmat = aaa->aaa_dmat;
165	sc->sc_bst = aaa->aaa_bst;
166	error = bus_space_map(sc->sc_bst, aaa->aaa_addr, DI_REG_SIZE,
167	    0, &sc->sc_bsh);
168	if (error != 0) {
169		aprint_error(": couldn't map registers (%d)\n", error);
170		return;
171	}
172
173	aprint_naive("\n");
174	aprint_normal(": Drive Interface\n");
175
176	callout_init(&sc->sc_timeout, 0);
177	callout_setfunc(&sc->sc_timeout, di_timeout, sc);
178	callout_init(&sc->sc_idle, 0);
179	callout_setfunc(&sc->sc_idle, di_idle, sc);
180
181	sc->sc_dma_size = MAXPHYS;
182	error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_dma_size, PAGE_SIZE, 0,
183	    sc->sc_dma_segs, 1, &nsegs, BUS_DMA_WAITOK);
184	if (error != 0) {
185		aprint_error_dev(self, "bus_dmamem_alloc failed: %d\n", error);
186		return;
187	}
188	error = bus_dmamem_map(sc->sc_dmat, sc->sc_dma_segs, nsegs,
189	    sc->sc_dma_size, &sc->sc_dma_addr, BUS_DMA_WAITOK);
190	if (error != 0) {
191		aprint_error_dev(self, "bus_dmamem_map failed: %d\n", error);
192		return;
193	}
194	error = bus_dmamap_create(sc->sc_dmat, sc->sc_dma_size, nsegs,
195	    sc->sc_dma_size, 0, BUS_DMA_WAITOK, &sc->sc_dma_map);
196	if (error != 0) {
197		aprint_error_dev(self, "bus_dmamap_create failed: %d\n", error);
198		return;
199	}
200	error = bus_dmamap_load(sc->sc_dmat, sc->sc_dma_map, sc->sc_dma_addr,
201	    sc->sc_dma_size, NULL, BUS_DMA_WAITOK);
202	if (error != 0) {
203		aprint_error_dev(self, "bus_dmamap_load failed: %d\n", error);
204		return;
205	}
206
207	memset(adapt, 0, sizeof(*adapt));
208	adapt->adapt_nchannels = 1;
209	adapt->adapt_request = di_request;
210	adapt->adapt_minphys = minphys;
211	adapt->adapt_dev = self;
212	adapt->adapt_max_periph = 1;
213	adapt->adapt_openings = 1;
214
215	memset(chan, 0, sizeof(*chan));
216	chan->chan_bustype = &scsi_bustype;
217	chan->chan_ntargets = 2;
218	chan->chan_nluns = 1;
219	chan->chan_id = 0;
220	chan->chan_flags = SCSIPI_CHAN_NOSETTLE;
221	chan->chan_adapter = adapt;
222
223	config_found(self, chan, scsiprint, CFARGS(.iattr = "scsi"));
224
225	ahb_intr_establish(aaa->aaa_irq, IPL_BIO, di_intr, sc,
226	    device_xname(self));
227
228	di_init_regs(sc);
229	callout_schedule(&sc->sc_idle, mstohz(DI_IDLE_TIMEOUT_MS));
230
231	pmf_device_register1(self, NULL, NULL, di_shutdown);
232}
233
234static bool
235di_shutdown(device_t dev, int how)
236{
237	struct di_softc *sc = device_private(dev);
238
239	di_reset(sc, false);
240
241	return true;
242}
243
244static void
245di_sense(struct scsipi_xfer *xs, uint8_t skey, uint8_t asc, uint8_t ascq)
246{
247	struct scsi_sense_data *sense = &xs->sense.scsi_sense;
248
249	xs->error = XS_SENSE;
250	sense->response_code = SSD_RCODE_CURRENT | SSD_RCODE_VALID;
251	sense->flags = skey;
252	sense->asc = asc;
253	sense->ascq = ascq;
254}
255
256static void
257di_request_error_sync(struct di_softc *sc, struct scsipi_xfer *xs)
258{
259	uint32_t imm;
260	int s;
261
262	s = splbio();
263	WR4(sc, DICMDBUF0, DI_CMD_REQUEST_ERROR);
264	WR4(sc, DICMDBUF1, 0);
265	WR4(sc, DICMDBUF2, 0);
266	WR4(sc, DILENGTH, 4);
267	WR4(sc, DICR, DICR_TSTART);
268	while (((RD4(sc, DISR) & DISR_TCINT)) == 0) {
269		delay(1);
270	}
271	imm = RD4(sc, DIMMBUF);
272	splx(s);
273
274	DPRINTF(sc->sc_dev, "ERR IMMBUF = 0x%08x\n", imm);
275	di_sense(xs, (imm >> 16) & 0xff, (imm >> 8) & 0xff, imm & 0xff);
276}
277
278static int
279di_transfer_error(struct di_softc *sc, struct scsipi_xfer *xs)
280{
281	if (xs == NULL) {
282		return 0;
283	}
284
285	DPRINTF(sc->sc_dev, "transfer error\n");
286
287	callout_stop(&sc->sc_timeout);
288	di_request_error_sync(sc, xs);
289	sc->sc_cur_xs = NULL;
290	scsipi_done(xs);
291
292	return 1;
293}
294
295static int
296di_transfer_complete(struct di_softc *sc, struct scsipi_xfer *xs)
297{
298	struct scsipi_generic *cmd;
299	struct scsipi_inquiry_data *inqbuf;
300	struct scsipi_read_cd_cap_data *cdcap;
301	struct di_response_inquiry *rinq;
302	uint32_t imm;
303	uint8_t *data;
304	char buf[5];
305
306	if (xs == NULL) {
307		DPRINTF(sc->sc_dev, "no active transfer\n");
308		return 0;
309	}
310
311	KASSERT(sc->sc_cur_xs == xs);
312
313	cmd = xs->cmd;
314
315	switch (cmd->opcode) {
316	case INQUIRY:
317		inqbuf = (struct scsipi_inquiry_data *)xs->data;
318		rinq = sc->sc_dma_addr;
319
320		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, sizeof(*rinq),
321		    BUS_DMASYNC_POSTREAD);
322
323		DPRINTF(sc->sc_dev, "revision_level %#x "
324				    "device_code %#x "
325				    "release_date %#x\n",
326				    rinq->revision_level,
327				    rinq->device_code,
328				    rinq->release_date);
329
330		memset(inqbuf, 0, sizeof(*inqbuf));
331		inqbuf->device = T_CDROM;
332		inqbuf->dev_qual2 = SID_REMOVABLE;
333		strncpy(inqbuf->vendor, "NINTENDO", sizeof(inqbuf->vendor));
334		snprintf(inqbuf->product, sizeof(inqbuf->product), "%08x",
335		    rinq->release_date);
336		snprintf(buf, sizeof(buf), "%04x", rinq->revision_level);
337		memcpy(inqbuf->revision, buf, sizeof(inqbuf->revision));
338		xs->resid = 0;
339		break;
340
341	case SCSI_TEST_UNIT_READY:
342	case SCSI_REQUEST_SENSE:
343		imm = RD4(sc, DIMMBUF);
344		DPRINTF(sc->sc_dev, "TUR IMMBUF = 0x%08x\n", imm);
345		switch ((imm >> 24) & 0xff) {
346		case 0:
347			di_sense(xs, (imm >> 16) & 0xff, (imm >> 8) & 0xff,
348			    imm & 0xff);
349			break;
350		default:
351			di_sense(xs, SKEY_MEDIUM_ERROR, 0, 0);
352			break;
353		}
354		break;
355
356	case SCSI_READ_6_COMMAND:
357	case READ_10:
358	case GPCMD_REPORT_KEY:
359		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, xs->datalen,
360		    BUS_DMASYNC_POSTREAD);
361		memcpy(xs->data, sc->sc_dma_addr, xs->datalen);
362		xs->resid = 0;
363		break;
364
365	case GPCMD_READ_DVD_STRUCTURE:
366		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, DVDBLOCKSIZE,
367		    BUS_DMASYNC_POSTREAD);
368		memcpy(xs->data + 4, sc->sc_dma_addr, xs->datalen - 4);
369		xs->resid = 0;
370		break;
371
372	case READ_CD_CAPACITY:
373		cdcap = (struct scsipi_read_cd_cap_data *)xs->data;
374
375		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, DVDBLOCKSIZE,
376		    BUS_DMASYNC_POSTREAD);
377		data = sc->sc_dma_addr;
378		_lto4b(DVDBLOCKSIZE, cdcap->length);
379		memcpy(cdcap->addr, &data[8], sizeof(cdcap->addr));
380		break;
381	}
382
383	sc->sc_cur_xs = NULL;
384	scsipi_done(xs);
385
386	return 1;
387}
388
389static int
390di_intr(void *priv)
391{
392	struct di_softc *sc = priv;
393	uint32_t sr, cvr;
394	int ret = 0;
395
396	sr = RD4(sc, DISR);
397	cvr = RD4(sc, DICVR);
398
399	if ((sr & DISR_DEINT) != 0) {
400		ret |= di_transfer_error(sc, sc->sc_cur_xs);
401	} else if ((sr & DISR_TCINT) != 0) {
402		ret |= di_transfer_complete(sc, sc->sc_cur_xs);
403	}
404
405	if ((cvr & DICVR_CVRINT) != 0) {
406		DPRINTF(sc->sc_dev, "drive %s\n",
407		    (cvr & DICVR_CVR) == 0 ? "closed" : "opened");
408		ret |= 1;
409	}
410
411	WR4(sc, DISR, sr);
412	WR4(sc, DICVR, cvr);
413
414	return ret;
415}
416
417static void
418di_timeout(void *priv)
419{
420	struct di_softc *sc = priv;
421	int s;
422
423	s = splbio();
424	if (sc->sc_cur_xs != NULL) {
425		struct scsipi_xfer *xs = sc->sc_cur_xs;
426
427		DPRINTF(sc->sc_dev, "command %#x timeout, DISR = %#x\n",
428		    xs->cmd->opcode, RD4(sc, DISR));
429		xs->error = XS_TIMEOUT;
430		scsipi_done(xs);
431
432		sc->sc_cur_xs = NULL;
433	}
434	splx(s);
435}
436
437static void
438di_idle(void *priv)
439{
440	struct di_softc *sc = priv;
441
442	if ((RD4(sc, DICVR) & DICVR_CVR) != 0) {
443		/* Cover is opened, nothing to do. */
444		return;
445	}
446
447	di_reset(sc, false);
448}
449
450static void
451di_start_request(struct di_softc *sc, struct scsipi_xfer *xs)
452{
453	KASSERT(sc->sc_cur_xs == NULL);
454	sc->sc_cur_xs = xs;
455	if (xs->timeout != 0) {
456		callout_schedule(&sc->sc_timeout, mstohz(xs->timeout) + 1);
457	} else {
458		DPRINTF(sc->sc_dev, "WARNING: xfer with no timeout!\n");
459		callout_schedule(&sc->sc_timeout, mstohz(15000));
460	}
461}
462
463static void
464di_init_regs(struct di_softc *sc)
465{
466	WR4(sc, DISR, DISR_BRKINT |
467		      DISR_TCINT | DISR_TCINTMASK |
468		      DISR_DEINT | DISR_DEINTMASK);
469	WR4(sc, DICVR, DICVR_CVRINT | DICVR_CVRINTMASK);
470}
471
472static void
473di_reset(struct di_softc *sc, bool spinup)
474{
475	uint32_t val;
476	int s;
477
478	DPRINTF(sc->sc_dev, "reset spinup=%d\n", spinup);
479
480	s = splhigh();
481
482	if (spinup) {
483		out32(HW_GPIOB_OUT, in32(HW_GPIOB_OUT) & ~__BIT(GPIO_DI_SPIN));
484	} else {
485		out32(HW_GPIOB_OUT, in32(HW_GPIOB_OUT) | __BIT(GPIO_DI_SPIN));
486	}
487
488	val = in32(HW_RESETS);
489	out32(HW_RESETS, val & ~RSTB_IODI);
490	delay(12);
491	out32(HW_RESETS, val | RSTB_IODI);
492
493	WR4(sc, DISR, DISR_BRKINT |
494		      DISR_TCINT | DISR_TCINTMASK |
495		      DISR_DEINT | DISR_DEINTMASK);
496	WR4(sc, DICVR, DICVR_CVRINT | DICVR_CVRINTMASK);
497
498	splx(s);
499}
500
501static void
502di_stop_motor(struct di_softc *sc, struct scsipi_xfer *xs, bool eject)
503{
504	uint32_t cmdflags = 0;
505	int s;
506
507	if (eject) {
508		cmdflags |= 1 << 17;
509	}
510
511	s = splbio();
512	WR4(sc, DICMDBUF0, DI_CMD_STOP_MOTOR | cmdflags);
513	WR4(sc, DICMDBUF1, 0);
514	WR4(sc, DICMDBUF2, 0);
515	WR4(sc, DILENGTH, 4);
516	WR4(sc, DICR, DICR_TSTART);
517	di_start_request(sc, xs);
518	splx(s);
519}
520
521static void
522di_request(struct scsipi_channel *chan, scsipi_adapter_req_t req, void *arg)
523{
524	struct di_softc *sc = device_private(chan->chan_adapter->adapt_dev);
525	struct scsipi_xfer *xs;
526	struct scsipi_generic *cmd;
527	struct scsipi_start_stop *ss;
528	struct scsi_prevent_allow_medium_removal *pamr;
529	uint32_t blkno;
530	int s;
531
532	if (req != ADAPTER_REQ_RUN_XFER) {
533		return;
534	}
535
536	callout_stop(&sc->sc_idle);
537
538	KASSERT(sc->sc_cur_xs == NULL);
539
540	xs = arg;
541	cmd = xs->cmd;
542
543	switch (cmd->opcode) {
544	case INQUIRY:
545		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map,
546		    0, sizeof(struct di_response_inquiry),
547		    BUS_DMASYNC_PREREAD);
548
549		s = splbio();
550		WR4(sc, DICMDBUF0, DI_CMD_INQUIRY);
551		WR4(sc, DICMDBUF1, 0);
552		WR4(sc, DILENGTH, sizeof(struct di_response_inquiry));
553		WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr);
554		WR4(sc, DICR, DICR_TSTART | DICR_DMA);
555		di_start_request(sc, xs);
556		splx(s);
557		break;
558
559	case SCSI_TEST_UNIT_READY:
560	case SCSI_REQUEST_SENSE:
561		s = splbio();
562		WR4(sc, DICMDBUF0, DI_CMD_REQUEST_ERROR);
563		WR4(sc, DICMDBUF1, 0);
564		WR4(sc, DICMDBUF2, 0);
565		WR4(sc, DILENGTH, 4);
566		WR4(sc, DICR, DICR_TSTART);
567		di_start_request(sc, xs);
568		splx(s);
569		break;
570
571	case SCSI_READ_6_COMMAND:
572	case READ_10:
573		if (cmd->opcode == SCSI_READ_6_COMMAND) {
574			blkno = _3btol(((struct scsi_rw_6 *)cmd)->addr);
575		} else {
576			KASSERT(cmd->opcode == READ_10);
577			blkno = _4btol(((struct scsipi_rw_10 *)cmd)->addr);
578		}
579
580		if (xs->datalen == 0) {
581			xs->error = XS_DRIVER_STUFFUP;
582			scsipi_done(xs);
583			break;
584		}
585
586		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map,
587		    0, xs->datalen, BUS_DMASYNC_PREREAD);
588
589		s = splbio();
590		WR4(sc, DICMDBUF0, DI_CMD_READ_DVD);
591		WR4(sc, DICMDBUF1, blkno);
592		WR4(sc, DICMDBUF2, howmany(xs->datalen, DVDBLOCKSIZE));
593		WR4(sc, DILENGTH, roundup(xs->datalen, DVDBLOCKSIZE));
594		WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr);
595		WR4(sc, DICR, DICR_TSTART | DICR_DMA);
596		di_start_request(sc, xs);
597		splx(s);
598		break;
599
600	case GPCMD_READ_DVD_STRUCTURE:
601		if (xs->datalen == 0) {
602			DPRINTF(sc->sc_dev, "zero datalen\n");
603			xs->error = XS_DRIVER_STUFFUP;
604			scsipi_done(xs);
605			break;
606		}
607
608		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map,
609		    0, xs->datalen, BUS_DMASYNC_PREREAD);
610
611		s = splbio();
612		WR4(sc, DICMDBUF0, DI_CMD_READ_DVD_STRUCT(cmd->bytes[6]));
613		WR4(sc, DICMDBUF1, 0);
614		WR4(sc, DICMDBUF2, 0);
615		WR4(sc, DILENGTH, roundup(xs->datalen, DVDBLOCKSIZE));
616		WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr);
617		WR4(sc, DICR, DICR_TSTART | DICR_DMA);
618		di_start_request(sc, xs);
619		splx(s);
620		break;
621
622	case GPCMD_REPORT_KEY:
623		if (xs->datalen == 0) {
624			DPRINTF(sc->sc_dev, "zero datalen\n");
625			xs->error = XS_DRIVER_STUFFUP;
626			scsipi_done(xs);
627			break;
628		}
629
630		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map,
631		    0, xs->datalen, BUS_DMASYNC_PREREAD);
632
633		s = splbio();
634		WR4(sc, DICMDBUF0, DI_CMD_REPORT_KEY(cmd->bytes[9] >> 2));
635		WR4(sc, DICMDBUF1, _4btol(&cmd->bytes[1]));
636		WR4(sc, DICMDBUF2, 0);
637		WR4(sc, DILENGTH, roundup(xs->datalen, 0x20));
638		WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr);
639		WR4(sc, DICR, DICR_TSTART | DICR_DMA);
640		di_start_request(sc, xs);
641		splx(s);
642		break;
643
644	case READ_CD_CAPACITY:
645		bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map,
646		    0, DVDBLOCKSIZE, BUS_DMASYNC_PREREAD);
647
648		s = splbio();
649		WR4(sc, DICMDBUF0, DI_CMD_READ_DVD_STRUCT(DVD_STRUCT_PHYSICAL));
650		WR4(sc, DICMDBUF1, 0);
651		WR4(sc, DICMDBUF2, 0);
652		WR4(sc, DILENGTH, DVDBLOCKSIZE);
653		WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr);
654		WR4(sc, DICR, DICR_TSTART | DICR_DMA);
655		di_start_request(sc, xs);
656		splx(s);
657		break;
658
659	case GET_CONFIGURATION:
660		memset(xs->data, 0, sizeof(struct scsipi_get_conf_data));
661		xs->resid = 0;
662		scsipi_done(xs);
663		break;
664
665	case READ_TOC:
666		memset(xs->data, 0, sizeof(struct scsipi_toc_header));
667		xs->resid = 0;
668		scsipi_done(xs);
669		break;
670
671	case READ_TRACKINFO:
672	case READ_DISCINFO:
673		di_sense(xs, SKEY_ILLEGAL_REQUEST, 0, 0);
674		scsipi_done(xs);
675		break;
676
677	case START_STOP:
678		ss = (struct scsipi_start_stop *)cmd;
679		if (ss->how == SSS_START) {
680			di_reset(sc, true);
681			scsipi_done(xs);
682		} else {
683			di_stop_motor(sc, xs, (ss->how & SSS_LOEJ) != 0);
684		}
685		break;
686
687	case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
688		pamr = (struct scsi_prevent_allow_medium_removal *)cmd;
689		sc->sc_pamr = pamr->how;
690		scsipi_done(xs);
691		break;
692
693	default:
694		DPRINTF(sc->sc_dev, "unsupported opcode %#x\n", cmd->opcode);
695		scsipi_done(xs);
696	}
697
698	if (!sc->sc_pamr) {
699		callout_schedule(&sc->sc_idle, mstohz(DI_IDLE_TIMEOUT_MS));
700	}
701}
702