alpm.c revision 59368
1/*-
2 * Copyright (c) 1998, 1999 Nicolas Souchu
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: head/sys/pci/alpm.c 59368 2000-04-18 15:15:39Z phk $
27 *
28 */
29
30/*
31 * Power Management support for the Acer M15x3 chipsets
32 */
33#include <sys/param.h>
34#include <sys/kernel.h>
35#include <sys/systm.h>
36#include <sys/module.h>
37#include <sys/bus.h>
38#include <sys/conf.h>
39#include <sys/uio.h>
40#include <sys/malloc.h>
41
42#include <machine/clock.h>
43
44#include <machine/bus_pio.h>
45#include <machine/bus_memio.h>
46#include <machine/bus.h>
47
48#include <pci/pcivar.h>
49#include <pci/pcireg.h>
50
51#include <dev/iicbus/iiconf.h>
52#include <dev/smbus/smbconf.h>
53#include "smbus_if.h"
54
55#include "alpm.h"
56
57#ifndef COMPAT_OLDPCI
58#error "The alpm device requires the old pci compatibility shims"
59#endif
60
61#define ALPM_DEBUG(x)	if (alpm_debug) (x)
62
63#ifdef DEBUG
64static int alpm_debug = 1;
65#else
66static int alpm_debug = 0;
67#endif
68
69#define ACER_M1543_PMU_ID	0x710110b9
70
71/* Uncomment this line to force another I/O base address for SMB */
72/* #define ALPM_SMBIO_BASE_ADDR	0x3a80 */
73
74/* I/O registers offsets - the base address is programmed via the
75 * SMBBA PCI configuration register
76 */
77#define SMBSTS		0x0	/* SMBus host/slave status register */
78#define SMBCMD		0x1	/* SMBus host/slave command register */
79#define SMBSTART	0x2	/* start to generate programmed cycle */
80#define SMBHADDR	0x3	/* host address register */
81#define SMBHDATA	0x4	/* data A register for host controller */
82#define SMBHDATB	0x5	/* data B register for host controller */
83#define SMBHBLOCK	0x6	/* block register for host controller */
84#define SMBHCMD		0x7	/* command register for host controller */
85
86/* SMBSTS masks */
87#define TERMINATE	0x80
88#define BUS_COLLI	0x40
89#define DEVICE_ERR	0x20
90#define SMI_I_STS	0x10
91#define HST_BSY		0x08
92#define IDL_STS		0x04
93#define HSTSLV_STS	0x02
94#define HSTSLV_BSY	0x01
95
96/* SMBCMD masks */
97#define SMB_BLK_CLR	0x80
98#define T_OUT_CMD	0x08
99#define ABORT_HOST	0x04
100
101/* SMBus commands */
102#define SMBQUICK	0x00
103#define SMBSRBYTE	0x10		/* send/receive byte */
104#define SMBWRBYTE	0x20		/* write/read byte */
105#define SMBWRWORD	0x30		/* write/read word */
106#define SMBWRBLOCK	0x40		/* write/read block */
107
108/* PCI configuration registers and masks
109 */
110#define COM		0x4
111#define COM_ENABLE_IO	0x1
112
113#define SMBBA		0x14
114
115#define ATPC		0x5b
116#define ATPC_SMBCTRL	0x04
117
118#define SMBHSI		0xe0
119#define SMBHSI_SLAVE	0x2
120#define SMBHSI_HOST	0x1
121
122#define SMBHCBC		0xe2
123#define SMBHCBC_CLOCK	0x70
124
125#define SMBCLOCK_149K	0x0
126#define SMBCLOCK_74K	0x20
127#define SMBCLOCK_37K	0x40
128#define SMBCLOCK_223K	0x80
129#define SMBCLOCK_111K	0xa0
130#define SMBCLOCK_55K	0xc0
131
132struct alpm_data {
133	int base;
134        bus_space_tag_t smbst;
135        bus_space_handle_t smbsh;
136	pcici_t tag;
137};
138struct alpm_data alpmdata[NALPM];
139
140struct alsmb_softc {
141	int base;
142	device_t smbus;
143	struct alpm_data *alpm;
144};
145
146#define ALPM_SMBINB(alsmb,register) \
147	(bus_space_read_1(alsmb->alpm->smbst, alsmb->alpm->smbsh, register))
148#define ALPM_SMBOUTB(alsmb,register,value) \
149	(bus_space_write_1(alsmb->alpm->smbst, alsmb->alpm->smbsh, register, value))
150
151static int alsmb_probe(device_t);
152static int alsmb_attach(device_t);
153static int alsmb_smb_callback(device_t, int, caddr_t *);
154static int alsmb_smb_quick(device_t dev, u_char slave, int how);
155static int alsmb_smb_sendb(device_t dev, u_char slave, char byte);
156static int alsmb_smb_recvb(device_t dev, u_char slave, char *byte);
157static int alsmb_smb_writeb(device_t dev, u_char slave, char cmd, char byte);
158static int alsmb_smb_readb(device_t dev, u_char slave, char cmd, char *byte);
159static int alsmb_smb_writew(device_t dev, u_char slave, char cmd, short word);
160static int alsmb_smb_readw(device_t dev, u_char slave, char cmd, short *word);
161static int alsmb_smb_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf);
162static int alsmb_smb_bread(device_t dev, u_char slave, char cmd, u_char count, char *byte);
163
164static devclass_t alsmb_devclass;
165
166static device_method_t alsmb_methods[] = {
167	/* device interface */
168	DEVMETHOD(device_probe,		alsmb_probe),
169	DEVMETHOD(device_attach,	alsmb_attach),
170
171	/* bus interface */
172	DEVMETHOD(bus_print_child,	bus_generic_print_child),
173
174	/* smbus interface */
175	DEVMETHOD(smbus_callback,	alsmb_smb_callback),
176	DEVMETHOD(smbus_quick,		alsmb_smb_quick),
177	DEVMETHOD(smbus_sendb,		alsmb_smb_sendb),
178	DEVMETHOD(smbus_recvb,		alsmb_smb_recvb),
179	DEVMETHOD(smbus_writeb,		alsmb_smb_writeb),
180	DEVMETHOD(smbus_readb,		alsmb_smb_readb),
181	DEVMETHOD(smbus_writew,		alsmb_smb_writew),
182	DEVMETHOD(smbus_readw,		alsmb_smb_readw),
183	DEVMETHOD(smbus_bwrite,		alsmb_smb_bwrite),
184	DEVMETHOD(smbus_bread,		alsmb_smb_bread),
185
186	{ 0, 0 }
187};
188
189static driver_t alsmb_driver = {
190	"alsmb",
191	alsmb_methods,
192	sizeof(struct alsmb_softc),
193};
194
195static const char* alpm_pci_probe(pcici_t tag, pcidi_t type);
196static void alpm_pci_attach(pcici_t tag, int unit);
197
198static u_long	alpm_count;
199
200static struct	pci_device alpm_device = {
201	"alpm",
202	alpm_pci_probe,
203	alpm_pci_attach,
204	&alpm_count
205};
206
207COMPAT_PCI_DRIVER (alpm, alpm_device);
208
209static const char*
210alpm_pci_probe(pcici_t tag, pcidi_t type)
211{
212	if (type == ACER_M1543_PMU_ID)
213		return ("AcerLabs M15x3 Power Management Unit");
214
215	return ((char *)0);
216}
217
218static void
219alpm_pci_attach(pcici_t tag, int unit)
220{
221	struct alpm_data *alpm;
222	u_long l;
223
224	if (unit >= NALPM) {
225		printf("alpm%d: attach: only %d units configured.\n",
226		        unit, NALPM);
227		return;
228	}
229	alpm = &alpmdata[unit];
230
231	alpm->tag = tag;
232
233	/* Unlock SMBIO base register access */
234	l = pci_cfgread(tag, ATPC, 1);
235	pci_cfgwrite(tag, ATPC, l & ~ATPC_SMBCTRL, 1);
236
237	if (bootverbose) {
238		l = pci_cfgread(tag, SMBHSI, 1);
239		printf("alsmb%d: %s/%s", unit,
240			(l & SMBHSI_HOST) ? "host":"nohost",
241			(l & SMBHSI_SLAVE) ? "slave":"noslave");
242
243		l = pci_cfgread(tag, SMBHCBC, 1);
244		switch (l & SMBHCBC_CLOCK) {
245		case SMBCLOCK_149K:
246			printf(" 149K");
247			break;
248		case SMBCLOCK_74K:
249			printf(" 74K");
250			break;
251		case SMBCLOCK_37K:
252			printf(" 37K");
253			break;
254		case SMBCLOCK_223K:
255			printf(" 223K");
256			break;
257		case SMBCLOCK_111K:
258			printf(" 111K");
259			break;
260		case SMBCLOCK_55K:
261			printf(" 55K");
262			break;
263		}
264	}
265
266	alpm->smbst = I386_BUS_SPACE_IO;
267
268#ifdef ALPM_SMBIO_BASE_ADDR
269	/* disable I/O */
270	l = pci_cfgread(tag, COM, 2);
271	pci_cfgwrite(tag, COM, l & ~COM_ENABLE_IO, 2);
272
273	/* set the I/O base address */
274	pci_cfgwrite(tag, SMBBA, ALPM_SMBIO_BASE_ADDR | 0x1, 4);
275
276	/* enable I/O */
277	pci_cfgwrite(tag, COM, l | COM_ENABLE_IO, 2);
278
279	alpm->smbsh = ALPM_SMBIO_BASE_ADDR;
280#else
281	alpm->smbsh = pci_cfgread(tag, SMBBA, 4) & ~0x1;
282#endif
283	if (bootverbose)
284		printf(" at 0x%x\n", alpm->smbsh);
285
286	/* XXX add the I2C interface to the root_bus until pcibus is ready */
287	device_add_child(root_bus, "alsmb", unit);
288
289	return;
290}
291
292/*
293 * Not a real probe, we know the device exists since the device has
294 * been added after the successfull pci probe.
295 */
296static int
297alsmb_probe(device_t dev)
298{
299	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
300
301	sc->alpm = &alpmdata[device_get_unit(dev)];
302
303	device_set_desc(dev, "Aladdin IV/V/Pro2 SMBus controller");
304
305	return (0);
306}
307
308static int
309alsmb_attach(device_t dev)
310{
311	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
312
313	/* allocate a new smbus device */
314	sc->smbus = smbus_alloc_bus(dev);
315
316	/* probe and attach the smbus */
317	device_probe_and_attach(sc->smbus);
318
319	return (0);
320}
321
322static int
323alsmb_smb_callback(device_t dev, int index, caddr_t *data)
324{
325	int error = 0;
326
327	switch (index) {
328	case SMB_REQUEST_BUS:
329	case SMB_RELEASE_BUS:
330		/* ok, bus allocation accepted */
331		break;
332	default:
333		error = EINVAL;
334	}
335
336	return (error);
337}
338
339static int
340alsmb_clear(struct alsmb_softc *sc)
341{
342	ALPM_SMBOUTB(sc, SMBSTS, 0xff);
343	DELAY(10);
344
345	return (0);
346}
347
348#if 0
349static int
350alsmb_abort(struct alsmb_softc *sc)
351{
352	ALPM_SMBOUTB(sc, SMBCMD, T_OUT_CMD | ABORT_HOST);
353
354	return (0);
355}
356#endif
357
358static int
359alsmb_idle(struct alsmb_softc *sc)
360{
361	u_char sts;
362
363	sts = ALPM_SMBINB(sc, SMBSTS);
364
365	ALPM_DEBUG(printf("alpm: idle? STS=0x%x\n", sts));
366
367	return (sts & IDL_STS);
368}
369
370/*
371 * Poll the SMBus controller
372 */
373static int
374alsmb_wait(struct alsmb_softc *sc)
375{
376	int count = 10000;
377	u_char sts;
378	int error;
379
380	/* wait for command to complete and SMBus controller is idle */
381	while(count--) {
382		DELAY(10);
383		sts = ALPM_SMBINB(sc, SMBSTS);
384		if (sts & SMI_I_STS)
385			break;
386	}
387
388	ALPM_DEBUG(printf("alpm: STS=0x%x\n", sts));
389
390	error = SMB_ENOERR;
391
392	if (!count)
393		error |= SMB_ETIMEOUT;
394
395	if (sts & TERMINATE)
396		error |= SMB_EABORT;
397
398	if (sts & BUS_COLLI)
399		error |= SMB_ENOACK;
400
401	if (sts & DEVICE_ERR)
402		error |= SMB_EBUSERR;
403
404	if (error != SMB_ENOERR)
405		alsmb_clear(sc);
406
407	return (error);
408}
409
410static int
411alsmb_smb_quick(device_t dev, u_char slave, int how)
412{
413	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
414	int error;
415
416	alsmb_clear(sc);
417	if (!alsmb_idle(sc))
418		return (EBUSY);
419
420	switch (how) {
421	case SMB_QWRITE:
422		ALPM_DEBUG(printf("alpm: QWRITE to 0x%x", slave));
423		ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
424		break;
425	case SMB_QREAD:
426		ALPM_DEBUG(printf("alpm: QREAD to 0x%x", slave));
427		ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
428		break;
429	default:
430		panic("%s: unknown QUICK command (%x)!", __FUNCTION__,
431			how);
432	}
433	ALPM_SMBOUTB(sc, SMBCMD, SMBQUICK);
434	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
435
436	error = alsmb_wait(sc);
437
438	ALPM_DEBUG(printf(", error=0x%x\n", error));
439
440	return (error);
441}
442
443static int
444alsmb_smb_sendb(device_t dev, u_char slave, char byte)
445{
446	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
447	int error;
448
449	alsmb_clear(sc);
450	if (!alsmb_idle(sc))
451		return (SMB_EBUSY);
452
453	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
454	ALPM_SMBOUTB(sc, SMBCMD, SMBSRBYTE);
455	ALPM_SMBOUTB(sc, SMBHDATA, byte);
456	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
457
458	error = alsmb_wait(sc);
459
460	ALPM_DEBUG(printf("alpm: SENDB to 0x%x, byte=0x%x, error=0x%x\n", slave, byte, error));
461
462	return (error);
463}
464
465static int
466alsmb_smb_recvb(device_t dev, u_char slave, char *byte)
467{
468	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
469	int error;
470
471	alsmb_clear(sc);
472	if (!alsmb_idle(sc))
473		return (SMB_EBUSY);
474
475	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
476	ALPM_SMBOUTB(sc, SMBCMD, SMBSRBYTE);
477	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
478
479	if ((error = alsmb_wait(sc)) == SMB_ENOERR)
480		*byte = ALPM_SMBINB(sc, SMBHDATA);
481
482	ALPM_DEBUG(printf("alpm: RECVB from 0x%x, byte=0x%x, error=0x%x\n", slave, *byte, error));
483
484	return (error);
485}
486
487static int
488alsmb_smb_writeb(device_t dev, u_char slave, char cmd, char byte)
489{
490	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
491	int error;
492
493	alsmb_clear(sc);
494	if (!alsmb_idle(sc))
495		return (SMB_EBUSY);
496
497	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
498	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE);
499	ALPM_SMBOUTB(sc, SMBHDATA, byte);
500	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
501	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
502
503	error = alsmb_wait(sc);
504
505	ALPM_DEBUG(printf("alpm: WRITEB to 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, byte, error));
506
507	return (error);
508}
509
510static int
511alsmb_smb_readb(device_t dev, u_char slave, char cmd, char *byte)
512{
513	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
514	int error;
515
516	alsmb_clear(sc);
517	if (!alsmb_idle(sc))
518		return (SMB_EBUSY);
519
520	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
521	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE);
522	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
523	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
524
525	if ((error = alsmb_wait(sc)) == SMB_ENOERR)
526		*byte = ALPM_SMBINB(sc, SMBHDATA);
527
528	ALPM_DEBUG(printf("alpm: READB from 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, *byte, error));
529
530	return (error);
531}
532
533static int
534alsmb_smb_writew(device_t dev, u_char slave, char cmd, short word)
535{
536	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
537	int error;
538
539	alsmb_clear(sc);
540	if (!alsmb_idle(sc))
541		return (SMB_EBUSY);
542
543	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
544	ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD);
545	ALPM_SMBOUTB(sc, SMBHDATA, word & 0x00ff);
546	ALPM_SMBOUTB(sc, SMBHDATB, (word & 0xff00) >> 8);
547	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
548	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
549
550	error = alsmb_wait(sc);
551
552	ALPM_DEBUG(printf("alpm: WRITEW to 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, word, error));
553
554	return (error);
555}
556
557static int
558alsmb_smb_readw(device_t dev, u_char slave, char cmd, short *word)
559{
560	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
561	int error;
562	u_char high, low;
563
564	alsmb_clear(sc);
565	if (!alsmb_idle(sc))
566		return (SMB_EBUSY);
567
568	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
569	ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD);
570	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
571	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
572
573	if ((error = alsmb_wait(sc)) == SMB_ENOERR) {
574		low = ALPM_SMBINB(sc, SMBHDATA);
575		high = ALPM_SMBINB(sc, SMBHDATB);
576
577		*word = ((high & 0xff) << 8) | (low & 0xff);
578	}
579
580	ALPM_DEBUG(printf("alpm: READW from 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, *word, error));
581
582	return (error);
583}
584
585static int
586alsmb_smb_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf)
587{
588	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
589	u_char remain, len, i;
590	int error = SMB_ENOERR;
591
592	alsmb_clear(sc);
593	if(!alsmb_idle(sc))
594		return (SMB_EBUSY);
595
596	remain = count;
597	while (remain) {
598		len = min(remain, 32);
599
600		ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
601
602		/* set the cmd and reset the
603		 * 32-byte long internal buffer */
604		ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR);
605
606		ALPM_SMBOUTB(sc, SMBHDATA, len);
607
608		/* fill the 32-byte internal buffer */
609		for (i=0; i<len; i++) {
610			ALPM_SMBOUTB(sc, SMBHBLOCK, buf[count-remain+i]);
611			DELAY(2);
612		}
613		ALPM_SMBOUTB(sc, SMBHCMD, cmd);
614		ALPM_SMBOUTB(sc, SMBSTART, 0xff);
615
616		if ((error = alsmb_wait(sc)) != SMB_ENOERR)
617			goto error;
618
619		remain -= len;
620	}
621
622error:
623	ALPM_DEBUG(printf("alpm: WRITEBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, count, cmd, error));
624
625	return (error);
626}
627
628static int
629alsmb_smb_bread(device_t dev, u_char slave, char cmd, u_char count, char *buf)
630{
631	struct alsmb_softc *sc = (struct alsmb_softc *)device_get_softc(dev);
632	u_char remain, len, i;
633	int error = SMB_ENOERR;
634
635	alsmb_clear(sc);
636	if (!alsmb_idle(sc))
637		return (SMB_EBUSY);
638
639	remain = count;
640	while (remain) {
641		ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
642
643		/* set the cmd and reset the
644		 * 32-byte long internal buffer */
645		ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR);
646
647		ALPM_SMBOUTB(sc, SMBHCMD, cmd);
648		ALPM_SMBOUTB(sc, SMBSTART, 0xff);
649
650		if ((error = alsmb_wait(sc)) != SMB_ENOERR)
651			goto error;
652
653		len = ALPM_SMBINB(sc, SMBHDATA);
654
655		/* read the 32-byte internal buffer */
656		for (i=0; i<len; i++) {
657			buf[count-remain+i] = ALPM_SMBINB(sc, SMBHBLOCK);
658			DELAY(2);
659		}
660
661		remain -= len;
662	}
663error:
664	ALPM_DEBUG(printf("alpm: READBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, count, cmd, error));
665
666	return (error);
667}
668
669DRIVER_MODULE(alsmb, root, alsmb_driver, alsmb_devclass, 0, 0);
670