octeon_ebt3000_cf.c revision 204789
1/***********************license start***************
2 *  Copyright (c) 2003-2008 Cavium Networks (support@cavium.com). All rights
3 *  reserved.
4 *
5 *
6 *  Redistribution and use in source and binary forms, with or without
7 *  modification, are permitted provided that the following conditions are
8 *  met:
9 *
10 *      * Redistributions of source code must retain the above copyright
11 *        notice, this list of conditions and the following disclaimer.
12 *
13 *      * Redistributions in binary form must reproduce the above
14 *        copyright notice, this list of conditions and the following
15 *        disclaimer in the documentation and/or other materials provided
16 *        with the distribution.
17 *
18 *      * Neither the name of Cavium Networks nor the names of
19 *        its contributors may be used to endorse or promote products
20 *        derived from this software without specific prior written
21 *        permission.
22 *
23 *  TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS"
24 *  AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS
25 *  OR WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH
26 *  RESPECT TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY
27 *  REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT
28 *  DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES
29 *  OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR
30 *  PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET
31 *  POSSESSION OR CORRESPONDENCE TO DESCRIPTION.  THE ENTIRE RISK ARISING OUT
32 *  OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU.
33 *
34 *
35 *  For any questions regarding licensing please contact marketing@caviumnetworks.com
36 *
37 ***********************license end**************************************/
38
39/*
40 *  octeon_ebt3000_cf.c
41 *
42 */
43
44#include <sys/cdefs.h>
45__FBSDID("$FreeBSD: head/sys/mips/cavium/octeon_ebt3000_cf.c 204789 2010-03-06 05:49:15Z jmallett $");
46
47#include <sys/param.h>
48#include <sys/bio.h>
49#include <sys/systm.h>
50#include <sys/sysctl.h>
51#include <sys/bus.h>
52#include <sys/kernel.h>
53#include <sys/module.h>
54#include <sys/rman.h>
55#include <sys/power.h>
56#include <sys/smp.h>
57#include <sys/time.h>
58#include <sys/timetc.h>
59#include <sys/malloc.h>
60
61#include <geom/geom.h>
62
63#include <machine/clock.h>
64#include <machine/locore.h>
65#include <machine/md_var.h>
66#include <machine/cpuregs.h>
67
68#include "octeon_ebt3000_cf.h"
69#include "driveid.h"
70#include <mips/cavium/octeon_pcmap_regs.h>
71
72/* ATA Commands */
73#define CMD_READ_SECTOR		0x20
74#define CMD_WRITE_SECTOR	0x30
75#define CMD_IDENTIFY		0xEC
76
77/* The ATA Task File */
78#define TF_DATA			0x00
79#define TF_ERROR		0x01
80#define TF_PRECOMP		0x01
81#define TF_SECTOR_COUNT		0x02
82#define TF_SECTOR_NUMBER	0x03
83#define TF_CYL_LSB		0x04
84#define TF_CYL_MSB		0x05
85#define TF_DRV_HEAD		0x06
86#define TF_STATUS		0x07
87#define TF_COMMAND		0x07
88
89/* Status Register */
90#define STATUS_BSY		0x80	/* Drive is busy */
91#define STATUS_RDY		0x40	/* Drive is ready */
92#define STATUS_DF		0x20	/* Device fault */
93#define STATUS_DRQ		0x08	/* Data can be transferred */
94
95/* Miscelaneous */
96#define SECTOR_SIZE		512
97#define WAIT_DELAY		1000
98#define NR_TRIES		1000
99#define SWAP_SHORT(x)		((x << 8) | (x >> 8))
100#define SWAP_LONG(x)		(((x << 24) & 0xFF000000) | ((x <<  8) & 0x00FF0000) | \
101				 ((x >> 8) & 0x0000FF00)  | ((x << 24) & 0x000000FF) )
102#define MODEL_STR_SIZE		40
103
104
105/* Globals */
106int	bus_width;
107void	*base_addr;
108
109/* Device softc */
110struct cf_priv {
111
112	device_t dev;
113	struct drive_param *drive_param;
114
115	struct bio_queue_head cf_bq;
116	struct g_geom *cf_geom;
117	struct g_provider *cf_provider;
118
119};
120
121/* Device parameters */
122struct drive_param{
123	union {
124		char buf[SECTOR_SIZE];
125		struct hd_driveid driveid;
126	} u;
127
128	char model[MODEL_STR_SIZE];
129	uint32_t nr_sectors;
130	uint16_t sector_size;
131	uint16_t heads;
132	uint16_t tracks;
133	uint16_t sec_track;
134
135} drive_param;
136
137/* GEOM class implementation */
138static g_access_t       cf_access;
139static g_start_t        cf_start;
140static g_ioctl_t        cf_ioctl;
141
142struct g_class g_cf_class = {
143        .name =         "CF",
144        .version =      G_VERSION,
145        .start =        cf_start,
146        .access =       cf_access,
147        .ioctl =        cf_ioctl,
148};
149
150/* Device methods */
151static int	cf_probe(device_t);
152static void	cf_identify(driver_t *, device_t);
153static int	cf_attach(device_t);
154static int	cf_attach_geom(void *, int);
155
156/* ATA methods */
157static int	cf_cmd_identify(void);
158static int	cf_cmd_write(uint32_t, uint32_t, void *);
159static int	cf_cmd_read(uint32_t, uint32_t, void *);
160static int	cf_wait_busy(void);
161static int	cf_send_cmd(uint32_t, uint8_t);
162static void	cf_attach_geom_proxy(void *arg, int flag);
163
164/* Miscelenous */
165static void	cf_swap_ascii(unsigned char[], char[]);
166
167
168/* ------------------------------------------------------------------- *
169 *                      cf_access()                                    *
170 * ------------------------------------------------------------------- */
171static int cf_access (struct g_provider *pp, int r, int w, int e)
172{
173
174	pp->sectorsize = drive_param.sector_size;
175        pp->stripesize = drive_param.heads * drive_param.sec_track * drive_param.sector_size;
176        pp->mediasize  = pp->stripesize * drive_param.tracks;
177
178	return (0);
179}
180
181
182/* ------------------------------------------------------------------- *
183 *                      cf_start()                                     *
184 * ------------------------------------------------------------------- */
185static void cf_start (struct bio *bp)
186{
187	int error;
188
189	/*
190	* Handle actual I/O requests. The request is passed down through
191	* the bio struct.
192	*/
193
194	if(bp->bio_cmd & BIO_GETATTR) {
195		if (g_handleattr_int(bp, "GEOM::fwsectors", drive_param.sec_track))
196                        return;
197                if (g_handleattr_int(bp, "GEOM::fwheads",   drive_param.heads))
198                        return;
199                g_io_deliver(bp, ENOIOCTL);
200                return;
201	}
202
203	if ((bp->bio_cmd & (BIO_READ | BIO_WRITE))) {
204
205		if (bp->bio_cmd & BIO_READ) {
206			error = cf_cmd_read(bp->bio_length / drive_param.sector_size,
207			    bp->bio_offset / drive_param.sector_size, bp->bio_data);
208		} else if (bp->bio_cmd & BIO_WRITE) {
209			error = cf_cmd_write(bp->bio_length / drive_param.sector_size,
210			    bp->bio_offset/drive_param.sector_size, bp->bio_data);
211		} else {
212			printf("%s: unrecognized bio_cmd %x.\n", __func__, bp->bio_cmd);
213			error = ENOTSUP;
214		}
215
216		if (error != 0) {
217			g_io_deliver(bp, error);
218			return;
219		}
220
221		bp->bio_resid = 0;
222		bp->bio_completed = bp->bio_length;
223		g_io_deliver(bp, 0);
224	}
225}
226
227
228static int cf_ioctl (struct g_provider *pp, u_long cmd, void *data, int fflag, struct thread *td)
229{
230    return (0);
231}
232
233
234/* ------------------------------------------------------------------- *
235 *                      cf_cmd_read()                                  *
236 * ------------------------------------------------------------------- *
237 *
238 *  Read nr_sectors from the device starting from start_sector.
239 */
240static int cf_cmd_read (uint32_t nr_sectors, uint32_t start_sector, void *buf)
241{
242	unsigned long lba;
243	uint32_t count;
244	uint16_t *ptr_16;
245	uint8_t  *ptr_8;
246	int error;
247
248//#define OCTEON_VISUAL_CF_0 1
249#ifdef OCTEON_VISUAL_CF_0
250        octeon_led_write_char(0, 'R');
251#endif
252	ptr_8  = (uint8_t*)buf;
253	ptr_16 = (uint16_t*)buf;
254	lba = start_sector;
255
256
257	while (nr_sectors--) {
258		error = cf_send_cmd(lba, CMD_READ_SECTOR);
259		if (error != 0) {
260			printf("%s: cf_send_cmd(CMD_READ_SECTOR) failed: %d\n", __func__, error);
261			return (error);
262		}
263
264		if (bus_width == 8) {
265			volatile uint8_t *task_file = (volatile uint8_t*)base_addr;
266        		volatile uint8_t dummy;
267			for (count = 0; count < SECTOR_SIZE; count++) {
268				*ptr_8++ = task_file[TF_DATA];
269				if ((count & 0xf) == 0) dummy = task_file[TF_STATUS];
270			}
271		} else {
272			volatile uint16_t *task_file = (volatile uint16_t*)base_addr;
273        		volatile uint16_t dummy;
274			for (count = 0; count < SECTOR_SIZE; count+=2) {
275				uint16_t temp;
276				temp = task_file[TF_DATA];
277				*ptr_16++ = SWAP_SHORT(temp);
278				if ((count & 0xf) == 0) dummy = task_file[TF_STATUS/2];
279			}
280		}
281
282		lba ++;
283	}
284#ifdef OCTEON_VISUAL_CF_0
285        octeon_led_write_char(0, ' ');
286#endif
287	return (0);
288}
289
290
291/* ------------------------------------------------------------------- *
292 *                      cf_cmd_write()                                 *
293 * ------------------------------------------------------------------- *
294 *
295 * Write nr_sectors to the device starting from start_sector.
296 */
297static int cf_cmd_write (uint32_t nr_sectors, uint32_t start_sector, void *buf)
298{
299	uint32_t lba;
300	uint32_t count;
301	uint16_t *ptr_16;
302	uint8_t  *ptr_8;
303	int error;
304
305//#define OCTEON_VISUAL_CF_1 1
306#ifdef OCTEON_VISUAL_CF_1
307        octeon_led_write_char(1, 'W');
308#endif
309	lba = start_sector;
310	ptr_8  = (uint8_t*)buf;
311	ptr_16 = (uint16_t*)buf;
312
313	while (nr_sectors--) {
314		error = cf_send_cmd(lba, CMD_WRITE_SECTOR);
315		if (error != 0) {
316			printf("%s: cf_send_cmd(CMD_WRITE_SECTOR) failed: %d\n", __func__, error);
317			return (error);
318		}
319
320		if (bus_width == 8) {
321			volatile uint8_t *task_file;
322        		volatile uint8_t dummy;
323
324			task_file = (volatile uint8_t *) base_addr;
325			for (count = 0; count < SECTOR_SIZE; count++) {
326				task_file[TF_DATA] =  *ptr_8++;
327				if ((count & 0xf) == 0) dummy = task_file[TF_STATUS];
328			}
329		} else {
330			volatile uint16_t *task_file;
331        		volatile uint16_t dummy;
332
333			task_file = (volatile uint16_t *) base_addr;
334			for (count = 0; count < SECTOR_SIZE; count+=2) {
335				uint16_t temp = *ptr_16++;
336				task_file[TF_DATA] =  SWAP_SHORT(temp);
337				if ((count & 0xf) == 0) dummy = task_file[TF_STATUS/2];
338			}
339		}
340
341		lba ++;
342	}
343#ifdef OCTEON_VISUAL_CF_1
344        octeon_led_write_char(1, ' ');
345#endif
346	return (0);
347}
348
349
350/* ------------------------------------------------------------------- *
351 *                      cf_cmd_identify()                              *
352 * ------------------------------------------------------------------- *
353 *
354 * Read parameters and other information from the drive and store
355 * it in the drive_param structure
356 *
357 */
358static int cf_cmd_identify (void)
359{
360	int count;
361	uint8_t status;
362	int error;
363
364	if (bus_width == 8) {
365        	volatile uint8_t *task_file;
366
367        	task_file = (volatile uint8_t *) base_addr;
368
369		while ((status = task_file[TF_STATUS]) & STATUS_BSY) {
370			DELAY(WAIT_DELAY);
371        	}
372
373        	task_file[TF_SECTOR_COUNT]  = 0;
374        	task_file[TF_SECTOR_NUMBER] = 0;
375        	task_file[TF_CYL_LSB]  = 0;
376        	task_file[TF_CYL_MSB]  = 0;
377        	task_file[TF_DRV_HEAD] = 0;
378        	task_file[TF_COMMAND]  = CMD_IDENTIFY;
379
380		error = cf_wait_busy();
381		if (error == 0) {
382			for (count = 0; count < SECTOR_SIZE; count++)
383				drive_param.u.buf[count] = task_file[TF_DATA];
384		}
385	} else {
386		volatile uint16_t *task_file;
387
388		task_file = (volatile uint16_t *) base_addr;
389
390		while ((status = (task_file[TF_STATUS/2]>>8)) & STATUS_BSY) {
391			DELAY(WAIT_DELAY);
392		}
393
394		task_file[TF_SECTOR_COUNT/2]  = 0; /* this includes TF_SECTOR_NUMBER */
395		task_file[TF_CYL_LSB/2]  = 0; /* this includes TF_CYL_MSB */
396		task_file[TF_DRV_HEAD/2] = 0 | (CMD_IDENTIFY<<8); /* this includes TF_COMMAND */
397
398		error = cf_wait_busy();
399		if (error == 0) {
400			for (count = 0; count < SECTOR_SIZE; count+=2) {
401				uint16_t temp;
402				temp = task_file[TF_DATA];
403
404				/* endianess will be swapped below */
405				drive_param.u.buf[count]   = (temp & 0xff);
406				drive_param.u.buf[count+1] = (temp & 0xff00)>>8;
407			}
408		}
409	}
410	if (error != 0) {
411		printf("%s: identify failed: %d\n", __func__, error);
412		return (error);
413	}
414
415	cf_swap_ascii(drive_param.u.driveid.model, drive_param.model);
416
417	drive_param.sector_size =  512;   //=  SWAP_SHORT (drive_param.u.driveid.sector_bytes);
418	drive_param.heads 	=  SWAP_SHORT (drive_param.u.driveid.cur_heads);
419	drive_param.tracks	=  SWAP_SHORT (drive_param.u.driveid.cur_cyls);
420	drive_param.sec_track   =  SWAP_SHORT (drive_param.u.driveid.cur_sectors);
421	drive_param.nr_sectors  =  SWAP_LONG  (drive_param.u.driveid.lba_capacity);
422
423	return (0);
424}
425
426
427/* ------------------------------------------------------------------- *
428 *                      cf_send_cmd()                                  *
429 * ------------------------------------------------------------------- *
430 *
431 * Send command to read/write one sector specified by lba.
432 *
433 */
434static int cf_send_cmd (uint32_t lba, uint8_t cmd)
435{
436	uint8_t status;
437
438	if (bus_width == 8) {
439		volatile uint8_t *task_file;
440
441		task_file = (volatile uint8_t *) base_addr;
442
443		while ( (status = task_file[TF_STATUS]) & STATUS_BSY) {
444			DELAY(WAIT_DELAY);
445		}
446
447		task_file[TF_SECTOR_COUNT]  = 1;
448		task_file[TF_SECTOR_NUMBER] = (lba & 0xff);
449		task_file[TF_CYL_LSB]  =  ((lba >> 8) & 0xff);
450		task_file[TF_CYL_MSB]  =  ((lba >> 16) & 0xff);
451		task_file[TF_DRV_HEAD] =  ((lba >> 24) & 0xff) | 0xe0;
452		task_file[TF_COMMAND]  =  cmd;
453
454	} else {
455		volatile uint16_t *task_file;
456
457		task_file = (volatile uint16_t *) base_addr;
458
459		while ( (status = (task_file[TF_STATUS/2]>>8)) & STATUS_BSY) {
460			DELAY(WAIT_DELAY);
461		}
462
463		task_file[TF_SECTOR_COUNT/2]  = 1 | ((lba & 0xff) << 8);
464		task_file[TF_CYL_LSB/2]  =  ((lba >> 8) & 0xff) | (((lba >> 16) & 0xff) << 8);
465		task_file[TF_DRV_HEAD/2] =  (((lba >> 24) & 0xff) | 0xe0) | (cmd << 8);
466
467	}
468
469	return (cf_wait_busy());
470}
471
472/* ------------------------------------------------------------------- *
473 *                      cf_wait_busy()                                 *
474 * ------------------------------------------------------------------- *
475 *
476 * Wait until the drive finishes a given command and data is
477 * ready to be transferred. This is done by repeatedly checking
478 * the BSY bit of the status register. When the controller is ready for
479 * data transfer, it clears the BSY bit and sets the DRQ bit.
480 *
481 * If the DF bit is ever set, we return error.
482 *
483 * This code originally spun on DRQ.  If that behavior turns out to be
484 * necessary, a flag can be added or this function can be called
485 * repeatedly as long as it is returning ENXIO.
486 */
487static int cf_wait_busy (void)
488{
489	uint8_t status;
490
491//#define OCTEON_VISUAL_CF_2 1
492#ifdef OCTEON_VISUAL_CF_2
493        static int where0 = 0;
494
495        octeon_led_run_wheel(&where0, 2);
496#endif
497
498	if (bus_width == 8) {
499		volatile uint8_t *task_file;
500		task_file = (volatile uint8_t *)base_addr;
501
502		status = task_file[TF_STATUS];
503		while ((status & STATUS_BSY) == STATUS_BSY) {
504			if ((status & STATUS_DF) != 0) {
505				printf("%s: device fault (status=%x)\n", __func__, status);
506				return (EIO);
507			}
508			DELAY(WAIT_DELAY);
509			status = task_file[TF_STATUS];
510		}
511	} else {
512		volatile uint16_t *task_file;
513		task_file = (volatile uint16_t *)base_addr;
514
515		status = task_file[TF_STATUS/2]>>8;
516		while ((status & STATUS_BSY) == STATUS_BSY) {
517			if ((status & STATUS_DF) != 0) {
518				printf("%s: device fault (status=%x)\n", __func__, status);
519				return (EIO);
520			}
521			DELAY(WAIT_DELAY);
522			status = (uint8_t)(task_file[TF_STATUS/2]>>8);
523		}
524	}
525	if ((status & STATUS_DRQ) == 0) {
526		printf("%s: device not ready (status=%x)\n", __func__, status);
527		return (ENXIO);
528	}
529
530#ifdef OCTEON_VISUAL_CF_2
531        octeon_led_write_char(2, ' ');
532#endif
533	return (0);
534}
535
536/* ------------------------------------------------------------------- *
537 *                      cf_swap_ascii()                                *
538 * ------------------------------------------------------------------- *
539 *
540 * The ascii string returned by the controller specifying
541 * the model of the drive is byte-swaped. This routine
542 * corrects the byte ordering.
543 *
544 */
545static void cf_swap_ascii (unsigned char str1[], char str2[])
546{
547	int i;
548
549	for(i = 0; i < MODEL_STR_SIZE; i++) {
550            str2[i] = str1[i^1];
551        }
552}
553
554
555/* ------------------------------------------------------------------- *
556 *                      cf_probe()                                     *
557 * ------------------------------------------------------------------- */
558
559static int cf_probe (device_t dev)
560{
561    	if (!octeon_board_real()) return 1;
562
563	if (device_get_unit(dev) != 0) {
564                panic("can't attach more devices\n");
565        }
566
567        device_set_desc(dev, "Octeon Compact Flash Driver");
568
569	return (cf_cmd_identify());
570}
571
572/* ------------------------------------------------------------------- *
573 *                      cf_identify()                                  *
574 * ------------------------------------------------------------------- *
575 *
576 * Find the bootbus region for the CF to determine
577 * 16 or 8 bit and check to see if device is
578 * inserted.
579 *
580 */
581static void cf_identify (driver_t *drv, device_t parent)
582{
583	uint8_t status;
584        int bus_region;
585	int count = 0;
586        octeon_mio_boot_reg_cfgx_t cfg;
587
588    	if (!octeon_board_real())
589		return;
590
591	base_addr = (void *) MIPS_PHYS_TO_KSEG0(OCTEON_CF_COMMON_BASE_ADDR);
592
593        for (bus_region = 0; bus_region < 8; bus_region++)
594        {
595                cfg.word64 = oct_read64(OCTEON_MIO_BOOT_REG_CFGX(bus_region));
596                if (cfg.bits.base == OCTEON_CF_COMMON_BASE_ADDR >> 16)
597                {
598                        bus_width = (cfg.bits.width) ? 16: 8;
599                        printf("Compact flash found in bootbus region %d (%d bit).\n", bus_region, bus_width);
600                        break;
601                }
602        }
603
604	if (bus_width == 8) {
605		volatile uint8_t *task_file;
606		task_file = (volatile uint8_t *) base_addr;
607		/* Check if CF is inserted */
608		while ( (status = task_file[TF_STATUS]) & STATUS_BSY){
609			if ((count++) == NR_TRIES )     {
610				printf("Compact Flash not present\n");
611				return;
612                	}
613			DELAY(WAIT_DELAY);
614        	}
615	} else {
616		volatile uint16_t *task_file;
617		task_file = (volatile uint16_t *) base_addr;
618		/* Check if CF is inserted */
619		while ( (status = (task_file[TF_STATUS/2]>>8)) & STATUS_BSY){
620			if ((count++) == NR_TRIES )     {
621				printf("Compact Flash not present\n");
622				return;
623                	}
624			DELAY(WAIT_DELAY);
625        	}
626	}
627
628	BUS_ADD_CHILD(parent, 0, "cf", 0);
629}
630
631
632/* ------------------------------------------------------------------- *
633 *                      cf_attach_geom()                               *
634 * ------------------------------------------------------------------- */
635
636static int cf_attach_geom (void *arg, int flag)
637{
638	struct cf_priv *cf_priv;
639
640	cf_priv = (struct cf_priv *) arg;
641	cf_priv->cf_geom = g_new_geomf(&g_cf_class, "cf%d", device_get_unit(cf_priv->dev));
642	cf_priv->cf_provider = g_new_providerf(cf_priv->cf_geom, cf_priv->cf_geom->name);
643	cf_priv->cf_geom->softc = cf_priv;
644        g_error_provider(cf_priv->cf_provider, 0);
645
646        return (0);
647}
648
649/* ------------------------------------------------------------------- *
650 *                      cf_attach_geom()                               *
651 * ------------------------------------------------------------------- */
652static void cf_attach_geom_proxy (void *arg, int flag)
653{
654    cf_attach_geom(arg, flag);
655}
656
657
658
659/* ------------------------------------------------------------------- *
660 *                      cf_attach()                                    *
661 * ------------------------------------------------------------------- */
662
663static int cf_attach (device_t dev)
664{
665	struct cf_priv *cf_priv;
666
667    	if (!octeon_board_real()) return 1;
668
669	cf_priv = device_get_softc(dev);
670	cf_priv->dev = dev;
671	cf_priv->drive_param = &drive_param;
672
673	g_post_event(cf_attach_geom_proxy, cf_priv, M_WAITOK, NULL);
674	bioq_init(&cf_priv->cf_bq);
675
676        return 0;
677}
678
679
680static device_method_t cf_methods[] = {
681        /* Device interface */
682        DEVMETHOD(device_probe,         cf_probe),
683        DEVMETHOD(device_identify,      cf_identify),
684        DEVMETHOD(device_attach,        cf_attach),
685        DEVMETHOD(device_detach,        bus_generic_detach),
686        DEVMETHOD(device_shutdown,      bus_generic_shutdown),
687
688        { 0, 0 }
689};
690
691static driver_t cf_driver = {
692        "cf",
693	cf_methods,
694	sizeof(struct cf_priv)
695};
696
697static devclass_t cf_devclass;
698
699DRIVER_MODULE(cf, nexus, cf_driver, cf_devclass, 0, 0);
700
701