Edit

IABSD.fr/src/sys/dev/usb/ugold.c

Branch :

  • Show log

    Commit

  • Author : miod
    Date : 2024-07-27 17:31:49
    Hash : 6bc6e70e
    Message : Allow for another TEMPerHUM version. From Sebastiaan Indesteege.

  • sys/dev/usb/ugold.c
  • /*	$OpenBSD: ugold.c,v 1.29 2024/07/27 17:31:49 miod Exp $   */
    
    /*
     * Copyright (c) 2013 Takayoshi SASANO <uaa@openbsd.org>
     * Copyright (c) 2013 Martin Pieuchot <mpi@openbsd.org>
     * Copyright (c) 2015 Joerg Jung <jung@openbsd.org>
     * Copyright (c) 2023 Miodrag Vallat.
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    /*
     * Driver for Microdia's HID based TEMPer and TEMPerHUM temperature and
     * humidity sensors
     */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/device.h>
    #include <sys/sensors.h>
    
    #include <dev/usb/usb.h>
    #include <dev/usb/usbhid.h>
    
    #include <dev/usb/usbdi.h>
    #include <dev/usb/usbdevs.h>
    #include <dev/usb/uhidev.h>
    
    #define UGOLD_INNER		0
    #define UGOLD_OUTER		1
    #define UGOLD_HUM		1
    #define UGOLD_MAX_SENSORS	2
    
    #define UGOLD_CMD_DATA		0x80
    #define UGOLD_CMD_INIT		0x82
    
    #define UGOLD_TYPE_INVALID	-1
    #define UGOLD_TYPE_SI7005	1
    #define UGOLD_TYPE_SI7006	2
    #define UGOLD_TYPE_SHT1X	3
    #define UGOLD_TYPE_GOLD		4
    #define UGOLD_TYPE_TEMPERX	5
    #define UGOLD_TYPE_DS75		6
    
    /*
     * This driver uses three known commands for the TEMPer and TEMPerHUM
     * devices.
     *
     * The first byte of the answer corresponds to the command and the
     * second one seems to be the size (in bytes) of the answer.
     *
     * The device always sends 8 bytes and if the length of the answer
     * is less than that, it just leaves the last bytes untouched.  That
     * is why most of the time the last n bytes of the answers are the
     * same.
     *
     * The type command below seems to generate two answers with a
     * string corresponding to the device, for example:
     *	'TEMPer1F' and '1.1Per1F' (here Per1F is repeated).
     */
    static uint8_t cmd_data[8] = { 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00 };
    static uint8_t cmd_init[8] = { 0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00 };
    static uint8_t cmd_type[8] = { 0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00 };
    /*
     * The following command is also recognized and reports some kind of status
     * byte (i.e. 87 xx 00 00 00 00 00 00).
    			     { 0x01, 0x87, 0xee, 0x01, 0x00, 0x00, 0x00, 0x00 };
     */
    
    struct ugold_softc;
    
    struct ugold_softc {
    	struct uhidev		 sc_hdev;
    	struct usbd_device	*sc_udev;
    
    	int			 sc_num_sensors;
    	int			 sc_type;
    
    	char			 sc_model[16 + 1];
    	unsigned int		 sc_model_len;
    
    	struct ksensor		 sc_sensor[UGOLD_MAX_SENSORS];
    	struct ksensordev	 sc_sensordev;
    	struct sensor_task	*sc_sensortask;
    
    	void		(*sc_intr)(struct ugold_softc *, uint8_t *, u_int);
    };
    
    const struct usb_devno ugold_devs[] = {
    	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPER },
    	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPERHUM },
    	{ USB_VENDOR_PCSENSORS, USB_PRODUCT_PCSENSORS_TEMPER },
    	{ USB_VENDOR_RDING, USB_PRODUCT_RDING_TEMPER },
    	{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_TEMPER },
    };
    
    int 	ugold_match(struct device *, void *, void *);
    void	ugold_attach(struct device *, struct device *, void *);
    int 	ugold_detach(struct device *, int);
    
    void	ugold_setup_sensors(struct ugold_softc *);
    void	ugold_intr(struct uhidev *, void *, u_int);
    void	ugold_ds75_intr(struct ugold_softc *, uint8_t *, u_int);
    void	ugold_si700x_intr(struct ugold_softc *, uint8_t *, u_int);
    void	ugold_refresh(void *);
    
    int	ugold_issue_cmd(struct ugold_softc *, uint8_t *, int);
    
    struct cfdriver ugold_cd = {
    	NULL, "ugold", DV_DULL
    };
    
    const struct cfattach ugold_ca = {
    	sizeof(struct ugold_softc), ugold_match, ugold_attach, ugold_detach,
    };
    
    int
    ugold_match(struct device *parent, void *match, void *aux)
    {
    	struct uhidev_attach_arg *uha = aux;
    	int size;
    	void *desc;
    
    	if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
    		return (UMATCH_NONE);
    
    	if (usb_lookup(ugold_devs, uha->uaa->vendor, uha->uaa->product) == NULL)
    		return (UMATCH_NONE);
    
    	/*
    	 * XXX Only match the sensor interface.
    	 *
    	 * Does it make sense to attach various uhidev(4) to these
    	 * non-standard HID devices?
    	 */
    	uhidev_get_report_desc(uha->parent, &desc, &size);
    	if (hid_is_collection(desc, size, uha->reportid,
    	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
    		return (UMATCH_NONE);
    
    	return (UMATCH_VENDOR_PRODUCT);
    
    }
    
    void
    ugold_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct ugold_softc *sc = (struct ugold_softc *)self;
    	struct uhidev_attach_arg *uha = aux;
    	int size, repid;
    	void *desc;
    
    	sc->sc_udev = uha->parent->sc_udev;
    	sc->sc_hdev.sc_parent = uha->parent;
    	sc->sc_hdev.sc_report_id = uha->reportid;
    	sc->sc_hdev.sc_intr = ugold_intr;
    	switch (uha->uaa->product) {
    	case USB_PRODUCT_MICRODIA_TEMPER:
    		sc->sc_intr = ugold_ds75_intr;
    		break;
    	case USB_PRODUCT_MICRODIA_TEMPERHUM:
    	case USB_PRODUCT_PCSENSORS_TEMPER:
    	case USB_PRODUCT_RDING_TEMPER:
    	case USB_PRODUCT_WCH2_TEMPER:
    		sc->sc_intr = ugold_si700x_intr;
    		break;
    	default:
    		printf(", unknown product\n");
    		return;
    	}
    
    	uhidev_get_report_desc(uha->parent, &desc, &size);
    	repid = uha->reportid;
    	sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
    	sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
    	sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
    
    	if (uhidev_open(&sc->sc_hdev)) {
    		printf(", unable to open interrupt pipe\n");
    		return;
    	}
    
    	strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.sc_dev.dv_xname,
    	    sizeof(sc->sc_sensordev.xname));
    
    	/* 0.166Hz */
    	sc->sc_sensortask = sensor_task_register(sc, ugold_refresh, 6);
    	if (sc->sc_sensortask == NULL) {
    		printf(", unable to register update task\n");
    		return;
    	}
    	printf("\n");
    
    	/* speed up sensor identification */
    	ugold_refresh(sc);
    
    	sensordev_install(&sc->sc_sensordev);
    }
    
    int
    ugold_detach(struct device *self, int flags)
    {
    	struct ugold_softc *sc = (struct ugold_softc *)self;
    	int i;
    
    	if (sc->sc_sensortask != NULL) {
    		sensor_task_unregister(sc->sc_sensortask);
    		sensordev_deinstall(&sc->sc_sensordev);
    	}
    
    	if (sc->sc_type != UGOLD_TYPE_INVALID) {
    		for (i = 0; i < sc->sc_num_sensors; i++)
    			sensor_detach(&sc->sc_sensordev, &sc->sc_sensor[i]);
    	}
    
    	if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
    		uhidev_close(&sc->sc_hdev);
    
    	return (0);
    }
    
    void
    ugold_setup_sensors(struct ugold_softc *sc)
    {
    	int i;
    
    	switch (sc->sc_type) {
    	default:
    		return;
    	case UGOLD_TYPE_SI7005:
    	case UGOLD_TYPE_SI7006:
    	case UGOLD_TYPE_SHT1X:
    	case UGOLD_TYPE_TEMPERX:
    		/* 1 temperature and 1 humidity sensor */
    		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
    		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
    		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
    		sc->sc_sensor[UGOLD_HUM].type = SENSOR_HUMIDITY;
    		strlcpy(sc->sc_sensor[UGOLD_HUM].desc, "RH",
    		    sizeof(sc->sc_sensor[UGOLD_HUM].desc));
    		break;
    	case UGOLD_TYPE_GOLD:
    	case UGOLD_TYPE_DS75:
    		/* up to 2 temperature sensors */
    		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
    		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
    		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
    		sc->sc_sensor[UGOLD_OUTER].type = SENSOR_TEMP;
    		strlcpy(sc->sc_sensor[UGOLD_OUTER].desc, "outer",
    		    sizeof(sc->sc_sensor[UGOLD_OUTER].desc));
    		break;
    	}
    	for (i = 0; i < sc->sc_num_sensors; i++) {
    		sc->sc_sensor[i].flags |= SENSOR_FINVALID;
    		sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
    	}
    }
    
    static void
    strnvis(char *dst, const char *src, size_t siz)
    {
    	char *start, *end;
    	int c;
    
    	for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) {
    		if (c >= 0x20 && c <= 0x7f) {
    			if (c == '\\') {
    				/* need space for the extra '\\' */
    				if (dst + 2 > end)
    					break;
    				*dst++ = '\\';
    			}
    			*dst++ = c;
    		} else {
    			if (dst + 4 > end)
    				break;
    			*dst++ = '\\';
    			*dst++ = ((u_char)c >> 6 & 07) + '0';
    			*dst++ = ((u_char)c >> 3 & 07) + '0';
    			*dst++ = ((u_char)c & 07) + '0';
    		}
    		src++;
    	}
    	if (siz > 0)
    		*dst = '\0';
    }
    
    static int
    ugold_ds75_temp(uint8_t msb, uint8_t lsb)
    {
    	/* DS75 12bit precision mode: 0.0625 degrees Celsius ticks */
    	return (((msb * 100) + ((lsb >> 4) * 25 / 4)) * 10000) + 273150000;
    }
    
    static void
    ugold_ds75_type(struct ugold_softc *sc)
    {
    	char model[4 * sizeof(sc->sc_model) + 1];
    
    	strnvis(model, sc->sc_model, sizeof model);
    
    	if (memcmp(sc->sc_model, "TEMPer1F", 8) == 0 ||
    	    memcmp(sc->sc_model, "TEMPer2F", 8) == 0 ||
    	    memcmp(sc->sc_model, "TEMPerF1", 8) == 0) {
    		sc->sc_type = UGOLD_TYPE_DS75;
    		ugold_setup_sensors(sc);
    		printf("%s: \"%s\", %d sensor%s"
    		       " type ds75/12bit (temperature)\n",
    		    sc->sc_hdev.sc_dev.dv_xname, model, sc->sc_num_sensors,
    		    (sc->sc_num_sensors == 1) ? "" : "s");
    		ugold_refresh(sc);
    		return;
    	}
    
    	printf("%s: unknown model \"%s\"\n",
    	    sc->sc_hdev.sc_dev.dv_xname, model);
    	sc->sc_num_sensors = 0;
    	sc->sc_type = UGOLD_TYPE_INVALID;
    }
    
    void
    ugold_ds75_intr(struct ugold_softc *sc, uint8_t *buf, u_int len)
    {
    	int temp;
    
    	switch (buf[0]) {
    	case UGOLD_CMD_INIT:
    		if (sc->sc_num_sensors != 0)
    			break;
    		sc->sc_num_sensors = imin(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
    		ugold_refresh(sc);
    		break;
    	case UGOLD_CMD_DATA:
    		switch (buf[1]) {
    		case 4:
    			temp = ugold_ds75_temp(buf[4], buf[5]);
    			sc->sc_sensor[UGOLD_OUTER].value = temp;
    			sc->sc_sensor[UGOLD_OUTER].flags &= ~SENSOR_FINVALID;
    			/* FALLTHROUGH */
    		case 2:
    			temp = ugold_ds75_temp(buf[2], buf[3]);
    			sc->sc_sensor[UGOLD_INNER].value = temp;
    			sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
    			break;
    		default:
    #ifdef UGOLD_DEBUG
    			printf("%s: invalid data length (%d bytes)\n",
    				sc->sc_hdev.sc_dev.dv_xname, buf[1]);
    #endif
    			break;
    		}
    		break;
    	default:
    		ugold_ds75_type(sc);
    		break;
    	}
    }
    
    static int
    ugold_si700x_temp(int type, uint8_t msb, uint8_t lsb)
    {
    	int temp = msb * 256 + lsb;
    
    	switch (type) { /* convert to mdegC */
    	case UGOLD_TYPE_SI7005: /* 14bit 32 codes per degC 0x0000 = -50 degC */
    		temp = (((temp & 0x3fff) * 1000) / 32) - 50000;
    		break;
    	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
    		temp = (((temp & ~3) * 21965) / 8192) - 46850;
    		break;
    	case UGOLD_TYPE_SHT1X:
    		temp = (temp * 1000) / 256;
    		break;
    	case UGOLD_TYPE_GOLD:
    	case UGOLD_TYPE_TEMPERX:
    		/* temp = temp / 100 to get degC, then * 1000 to get mdegC */
    		temp = temp * 10;
    		break;
    	default:
    		temp = 0;
    	}
    
    	return temp;
    }
    
    static int
    ugold_si700x_rhum(int type, uint8_t msb, uint8_t lsb, int temp)
    {
    	int rhum = msb * 256 + lsb;
    
    	switch (type) { /* convert to m%RH */
    	case UGOLD_TYPE_SI7005: /* 12bit 16 codes per %RH 0x0000 = -24 %RH */
    		rhum = (((rhum & 0x0fff) * 1000) / 16) - 24000;
    #if 0		/* todo: linearization and temperature compensation */
    		rhum -= -0.00393 * rhum * rhum + 0.4008 * rhum - 4.7844;
    		rhum += (temp - 30) * (0.00237 * rhum + 0.1973);
    #endif
    		break;
    	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
    		rhum = (((rhum & ~3) * 15625) / 8192) - 6000;
    		break;
    	case UGOLD_TYPE_SHT1X: /* 16 bit */
    		rhum = rhum * 32;
    		break;
    	case UGOLD_TYPE_TEMPERX:
    		rhum = rhum * 10;
    		break;
    	default:
    		rhum = 0;
    	}
    
    	/* limit the humidity to valid values */
    	if (rhum < 0)
    		rhum = 0;
    	else if (rhum > 100000)
    		rhum = 100000;
    	return rhum;
    }
    
    static void
    ugold_si700x_type(struct ugold_softc *sc)
    {
    	char model[4 * sizeof(sc->sc_model) + 1];
    	const char *descr;
    	int nsensors = 0;
    
    	strnvis(model, sc->sc_model, sizeof model);
    
    	/* TEMPerHUM prefix */
    	if (sc->sc_model_len >= 9 &&
    	    memcmp(sc->sc_model, "TEMPerHum", 9) == 0) {
    		if (memcmp(sc->sc_model + 9, "M12V1.0", 16 - 9) == 0) {
    			sc->sc_type = UGOLD_TYPE_SI7005;
    			descr = "si7005 (temperature and humidity)";
    			goto identified;
    		}
    		if (memcmp(sc->sc_model + 9, "M12V1.2", 16 - 9) == 0) {
    			sc->sc_type = UGOLD_TYPE_SI7006;
    			descr = "si7006 (temperature and humidity)";
    			goto identified;
    		}
    	}
    	if (sc->sc_model_len >= 9 &&
    	    memcmp(sc->sc_model, "TEMPerHUM", 9) == 0) {
    		if (memcmp(sc->sc_model + 9, "_V3.9  ", 16 - 9) == 0 ||
    		    memcmp(sc->sc_model + 9, "_V4.0  ", 16 - 9) == 0 ||
    		    memcmp(sc->sc_model + 9, "_V4.1\0\0", 16 - 9) == 0) {
    			sc->sc_type = UGOLD_TYPE_TEMPERX;
    			descr = "temperx (temperature and humidity)";
    			goto identified;
    		}
    	}
    
    	/* TEMPerX prefix */
    	if (sc->sc_model_len >= 8 &&
    	    memcmp(sc->sc_model, "TEMPerX_", 8) == 0) {
    		if (memcmp(sc->sc_model + 8, "V3.1    ", 16 - 8) == 0 ||
    		    memcmp(sc->sc_model + 8, "V3.3    ", 16 - 8) == 0) {
    			sc->sc_type = UGOLD_TYPE_TEMPERX;
    			descr = "temperx (temperature and humidity)";
    			goto identified;
    		}
    	}
    
    	/* TEMPer1F or TEMPer2_ prefixes */
    	if (sc->sc_model_len >= 16 &&
    	    memcmp(sc->sc_model, "TEMPer1F_H1V1.5F", 16) == 0) {
    		sc->sc_type = UGOLD_TYPE_SHT1X;
    		descr = "sht1x (temperature and humidity)";
    		goto identified;
    	}
    	if (sc->sc_model_len >= 16 &&
    	    (memcmp(sc->sc_model, "TEMPer1F_V4.1\0\0\0", 16) == 0 ||
    	     memcmp(sc->sc_model, "TEMPer2_V4.1\0\0\0\0", 16) == 0)) {
    		sc->sc_type = UGOLD_TYPE_GOLD;
    		/*
    		 * TEMPer1F devices lack the internal sensor, but will never
    		 * report data for it, so it will never get marked as valid.
    		 * We thus keep the value of sc_num_sensors unchanged at 2,
    		 * and make sure we will only report one single sensor below.
    		 */
    		if (sc->sc_model[6] == '1')
    			nsensors = 1;
    		descr = "gold (temperature only)";
    		goto identified;
    	}
    
    	/* TEMPerGold prefix */
    	if (sc->sc_model_len >= 11 &&
    	    memcmp(sc->sc_model, "TEMPerGold_", 11) == 0) {
    		/*
    		 * All V3.something models ought to work, but better be
    		 * safe than sorry, and TEMPerHum models have been known
    		 * to use slightly different sensors between models.
    		 */
    		if (memcmp(sc->sc_model + 11, "V3.1 ", 16 - 11) == 0 ||
    		    memcmp(sc->sc_model + 11, "V3.4 ", 16 - 11) == 0 ||
    		    memcmp(sc->sc_model + 11, "V3.5 ", 16 - 11) == 0) {
    			sc->sc_type = UGOLD_TYPE_GOLD;
    			sc->sc_num_sensors = 1;
    			descr = "gold (temperature only)";
    			goto identified;
    		}
    	}
    	
    	printf("%s: unknown model \"%s\"\n",
    	    sc->sc_hdev.sc_dev.dv_xname, model);
    	sc->sc_num_sensors = 0;
    	sc->sc_type = UGOLD_TYPE_INVALID;
    	return;
    
     identified:
    	ugold_setup_sensors(sc);
    	if (nsensors == 0)
    		nsensors = sc->sc_num_sensors;
    	printf("%s: \"%s\", %d sensor%s type %s\n", sc->sc_hdev.sc_dev.dv_xname,
    	    model, nsensors, (nsensors == 1) ? "" : "s", descr);
    	ugold_refresh(sc);
    }
    
    void
    ugold_si700x_intr(struct ugold_softc *sc, uint8_t *buf, u_int len)
    {
    	int temp, sensor, rhum;
    
    	switch (buf[0]) {
    	case UGOLD_CMD_INIT:
    		if (sc->sc_num_sensors != 0)
    			break;
    		/* XXX some devices report 0x04 here */
    		sc->sc_num_sensors = imin(buf[1], UGOLD_MAX_SENSORS);
    		ugold_refresh(sc);
    		break;
    	case UGOLD_CMD_DATA:
    		if (sc->sc_type == UGOLD_TYPE_GOLD) {
    			if (buf[1] == 0x80)
    				sensor = UGOLD_INNER;
    			else if (buf[1] == 0x01)
    				sensor = UGOLD_OUTER;
    			else
    				sensor = -1;
    		} else {
    			if (buf[1] == 0x04 || buf[1] == 0x20 ||
    			    buf[1] == 0x40 || buf[1] == 0x80)
    				sensor = UGOLD_INNER;
    			else
    				sensor = -1;
    		}
    		if (sensor < 0) {
    			/* unexpected data, ignore */
    #ifdef UGOLD_DEBUG
    			printf("%s: unexpected sensor id %02x\n",
    			    sc->sc_hdev.sc_dev.dv_xname, buf[1]);
    #endif
    			break;
    		}
    
    		temp = ugold_si700x_temp(sc->sc_type, buf[2], buf[3]);
    		sc->sc_sensor[sensor].value = (temp * 1000) + 273150000;
    		/*
    		 * TEMPer1F and TEMPer2 report 200C when the sensor probe is
    		 * missing or not plugged correctly.
    		 */
    		if (sc->sc_type == UGOLD_TYPE_GOLD && temp == 200000)
    			sc->sc_sensor[sensor].flags |= SENSOR_FINVALID;
    		else
    			sc->sc_sensor[sensor].flags &= ~SENSOR_FINVALID;
    
    		if (sc->sc_type != UGOLD_TYPE_GOLD) {
    			rhum = ugold_si700x_rhum(sc->sc_type, buf[4], buf[5], temp);
    			sc->sc_sensor[UGOLD_HUM].value = rhum;
    			sc->sc_sensor[UGOLD_HUM].flags &= ~SENSOR_FINVALID;
    		}
    		break;
    	default:
    		ugold_si700x_type(sc);
    		break;
    	}
    }
    
    void
    ugold_intr(struct uhidev *addr, void *ibuf, u_int len)
    {
    	struct ugold_softc *sc = (struct ugold_softc *)addr;
    	uint8_t *buf = ibuf;
    	unsigned long chunk;
    
    #ifdef UGOLD_DEBUG
    	{
    		printf("%s: %u bytes\n", sc->sc_hdev.sc_dev.dv_xname, len);
    		u_int i;
    		for (i = 0; i < len; i++) {
    			if (i != 0 && (i % 8) == 0)
    				printf("\n");
    			printf("%02x ", buf[i]);
    		}
    		printf("\n");
    	}
    #endif
    
    	switch (buf[0]) {
    	case UGOLD_CMD_INIT:
    	case UGOLD_CMD_DATA:
    		(*sc->sc_intr)(sc, buf, len);
    		break;
    	default:
    		if (!sc->sc_type) {
    			/*
    			 * During initialization, some devices need a bit
    			 * more time to submit their identification string.
    			 */
    			if (len == sc->sc_model_len &&
    			    !memcmp(sc->sc_model, buf, len)) {
    #ifdef UGOLD_DEBUG
    				printf("%s: duplicate string component\n",
    				    sc->sc_hdev.sc_dev.dv_xname);
    #endif
    				break;
    			}
    			/*
    			 * Exact sensor type is not known yet, type command
    			 * returns arbitrary string.
    			 */
    			chunk = ulmin(len,
    			    sizeof(sc->sc_model) - 1 - sc->sc_model_len);
    			if (chunk != 0) {
    				memcpy(sc->sc_model + sc->sc_model_len, buf,
    				    chunk);
    				sc->sc_model_len += chunk;
    			}
    			if (sc->sc_model_len > 8) {
    				/* should have enough data now */
    				(*sc->sc_intr)(sc, buf, len);
    			}
    			break;
    		}
    		printf("%s: unknown command 0x%02x\n",
    		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
    		break;
    	}
    }
    
    void
    ugold_refresh(void *arg)
    {
    	struct ugold_softc *sc = arg;
    	int i;
    
    	/*
    	 * Don't waste time talking to the device if we don't understand
    	 * its language.
    	 */
    	if (sc->sc_type == UGOLD_TYPE_INVALID)
    		return;
    
    	if (!sc->sc_num_sensors) {
    		ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
    		return;
    	}
    	if (!sc->sc_type) {
    		ugold_issue_cmd(sc, cmd_type, sizeof(cmd_type));
    		return;
    	}
    
    	if (ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data))) {
    		for (i = 0; i < sc->sc_num_sensors; i++)
    			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
    	}
    }
    
    int
    ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
    {
    	int actlen;
    
    	actlen = uhidev_set_report_async(sc->sc_hdev.sc_parent,
    	    UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, cmd, len);
    	return (actlen != len);
    }