1284272Shselasky/* $OpenBSD: ugold.c,v 1.7 2014/12/11 18:39:27 mpi Exp $ */ 2284272Shselasky 3284272Shselasky/* 4284272Shselasky * Copyright (c) 2013 Takayoshi SASANO <sasano@openbsd.org> 5284272Shselasky * Copyright (c) 2013 Martin Pieuchot <mpi@openbsd.org> 6284272Shselasky * 7284272Shselasky * Permission to use, copy, modify, and distribute this software for any 8284272Shselasky * purpose with or without fee is hereby granted, provided that the above 9284272Shselasky * copyright notice and this permission notice appear in all copies. 10284272Shselasky * 11284272Shselasky * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES 12284272Shselasky * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13284272Shselasky * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14284272Shselasky * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15284272Shselasky * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16284272Shselasky * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17284272Shselasky * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18284272Shselasky */ 19284272Shselasky 20284272Shselasky/* Driver for Microdia's HID based TEMPer Temperature sensor */ 21284272Shselasky 22284272Shselasky#include <sys/cdefs.h> 23284272Shselasky__FBSDID("$FreeBSD$"); 24284272Shselasky 25284272Shselasky#include <sys/stdint.h> 26284272Shselasky#include <sys/stddef.h> 27284272Shselasky#include <sys/param.h> 28284272Shselasky#include <sys/queue.h> 29284272Shselasky#include <sys/types.h> 30284272Shselasky#include <sys/systm.h> 31284272Shselasky#include <sys/kernel.h> 32284272Shselasky#include <sys/bus.h> 33284272Shselasky#include <sys/module.h> 34284272Shselasky#include <sys/lock.h> 35284272Shselasky#include <sys/mutex.h> 36284272Shselasky#include <sys/condvar.h> 37284272Shselasky#include <sys/sysctl.h> 38284272Shselasky#include <sys/sx.h> 39284272Shselasky#include <sys/unistd.h> 40284272Shselasky#include <sys/callout.h> 41284272Shselasky#include <sys/malloc.h> 42284272Shselasky#include <sys/priv.h> 43284272Shselasky#include <sys/conf.h> 44284272Shselasky 45284272Shselasky#include <dev/usb/usb.h> 46284272Shselasky#include <dev/usb/usbdi.h> 47284272Shselasky#include <dev/usb/usbhid.h> 48284272Shselasky#include <dev/usb/usb_process.h> 49284272Shselasky#include <dev/usb/usbdi_util.h> 50284272Shselasky#include "usbdevs.h" 51284272Shselasky 52284272Shselasky#define USB_DEBUG_VAR usb_debug 53284272Shselasky#include <dev/usb/usb_debug.h> 54284272Shselasky 55284272Shselasky#define UGOLD_INNER 0 56284272Shselasky#define UGOLD_OUTER 1 57284272Shselasky#define UGOLD_MAX_SENSORS 2 58284272Shselasky 59284272Shselasky#define UGOLD_CMD_DATA 0x80 60284272Shselasky#define UGOLD_CMD_INIT 0x82 61284272Shselasky 62284272Shselaskyenum { 63284272Shselasky UGOLD_INTR_DT, 64284272Shselasky UGOLD_N_TRANSFER, 65284272Shselasky}; 66284272Shselasky 67284272Shselasky/* 68284272Shselasky * This driver only uses two of the three known commands for the 69284272Shselasky * TEMPerV1.2 device. 70284272Shselasky * 71284272Shselasky * The first byte of the answer corresponds to the command and the 72284272Shselasky * second one seems to be the size (in bytes) of the answer. 73284272Shselasky * 74284272Shselasky * The device always sends 8 bytes and if the length of the answer 75284272Shselasky * is less than that, it just leaves the last bytes untouched. That 76284272Shselasky * is why most of the time the last n bytes of the answers are the 77284272Shselasky * same. 78284272Shselasky * 79284272Shselasky * The third command below seems to generate two answers with a 80284272Shselasky * string corresponding to the device, for example: 81284272Shselasky * 'TEMPer1F' and '1.1Per1F' (here Per1F is repeated). 82284272Shselasky */ 83284272Shselaskystatic uint8_t cmd_data[8] = {0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00}; 84284272Shselaskystatic uint8_t cmd_init[8] = {0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00}; 85284272Shselasky 86284272Shselasky#if 0 87284272Shselaskystatic uint8_t cmd_type[8] = {0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00}; 88284272Shselasky 89284272Shselasky#endif 90284272Shselasky 91284272Shselaskystruct ugold_softc; 92284272Shselaskystruct ugold_readout_msg { 93284272Shselasky struct usb_proc_msg hdr; 94284272Shselasky struct ugold_softc *sc; 95284272Shselasky}; 96284272Shselasky 97284272Shselaskystruct ugold_softc { 98284272Shselasky struct usb_device *sc_udev; 99284272Shselasky struct usb_xfer *sc_xfer[UGOLD_N_TRANSFER]; 100284272Shselasky 101284272Shselasky struct callout sc_callout; 102284272Shselasky struct mtx sc_mtx; 103284272Shselasky struct ugold_readout_msg sc_readout_msg[2]; 104284272Shselasky 105284272Shselasky int sc_num_sensors; 106284272Shselasky int sc_sensor[UGOLD_MAX_SENSORS]; 107284272Shselasky int sc_calib[UGOLD_MAX_SENSORS]; 108284272Shselasky int sc_valid[UGOLD_MAX_SENSORS]; 109284272Shselasky uint8_t sc_report_id; 110284272Shselasky uint8_t sc_iface_index[2]; 111284272Shselasky}; 112284272Shselasky 113284272Shselasky/* prototypes */ 114284272Shselasky 115284272Shselaskystatic device_probe_t ugold_probe; 116284272Shselaskystatic device_attach_t ugold_attach; 117284272Shselaskystatic device_detach_t ugold_detach; 118284272Shselasky 119284272Shselaskystatic usb_proc_callback_t ugold_readout_msg; 120284272Shselasky 121284272Shselaskystatic usb_callback_t ugold_intr_callback; 122284272Shselasky 123284272Shselaskystatic devclass_t ugold_devclass; 124284272Shselasky 125284272Shselaskystatic device_method_t ugold_methods[] = { 126284272Shselasky DEVMETHOD(device_probe, ugold_probe), 127284272Shselasky DEVMETHOD(device_attach, ugold_attach), 128284272Shselasky DEVMETHOD(device_detach, ugold_detach), 129284272Shselasky 130284272Shselasky DEVMETHOD_END 131284272Shselasky}; 132284272Shselasky 133284272Shselaskystatic driver_t ugold_driver = { 134284272Shselasky .name = "ugold", 135284272Shselasky .methods = ugold_methods, 136284272Shselasky .size = sizeof(struct ugold_softc), 137284272Shselasky}; 138284272Shselasky 139292080Simpstatic const STRUCT_USB_HOST_ID ugold_devs[] = { 140292080Simp {USB_VPI(USB_VENDOR_CHICONY2, USB_PRODUCT_CHICONY2_TEMPER, 0)}, 141292080Simp}; 142292080Simp 143284272ShselaskyDRIVER_MODULE(ugold, uhub, ugold_driver, ugold_devclass, NULL, NULL); 144284272ShselaskyMODULE_DEPEND(ugold, usb, 1, 1, 1); 145284272ShselaskyMODULE_VERSION(ugold, 1); 146292080SimpUSB_PNP_HOST_INFO(ugold_devs); 147284272Shselasky 148284272Shselaskystatic const struct usb_config ugold_config[UGOLD_N_TRANSFER] = { 149284272Shselasky 150284272Shselasky [UGOLD_INTR_DT] = { 151284272Shselasky .type = UE_INTERRUPT, 152284272Shselasky .endpoint = UE_ADDR_ANY, 153284272Shselasky .direction = UE_DIR_IN, 154284272Shselasky .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, 155284272Shselasky .bufsize = 0, /* use wMaxPacketSize */ 156284272Shselasky .callback = &ugold_intr_callback, 157284272Shselasky .if_index = 1, 158284272Shselasky }, 159284272Shselasky}; 160284272Shselasky 161284272Shselaskystatic void 162284272Shselaskyugold_timeout(void *arg) 163284272Shselasky{ 164284272Shselasky struct ugold_softc *sc = arg; 165284272Shselasky 166284272Shselasky usb_proc_explore_lock(sc->sc_udev); 167284272Shselasky (void)usb_proc_explore_msignal(sc->sc_udev, 168284272Shselasky &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]); 169284272Shselasky usb_proc_explore_unlock(sc->sc_udev); 170284272Shselasky 171284272Shselasky callout_reset(&sc->sc_callout, 6 * hz, &ugold_timeout, sc); 172284272Shselasky} 173284272Shselasky 174284272Shselaskystatic int 175284272Shselaskyugold_probe(device_t dev) 176284272Shselasky{ 177284272Shselasky struct usb_attach_arg *uaa; 178284272Shselasky 179284272Shselasky uaa = device_get_ivars(dev); 180284272Shselasky if (uaa->usb_mode != USB_MODE_HOST) 181284272Shselasky return (ENXIO); 182284272Shselasky if (uaa->info.bInterfaceClass != UICLASS_HID) 183284272Shselasky return (ENXIO); 184284272Shselasky if (uaa->info.bIfaceIndex != 0) 185284272Shselasky return (ENXIO); 186284272Shselasky 187284272Shselasky return (usbd_lookup_id_by_uaa(ugold_devs, sizeof(ugold_devs), uaa)); 188284272Shselasky} 189284272Shselasky 190284272Shselaskystatic int 191284272Shselaskyugold_attach(device_t dev) 192284272Shselasky{ 193284272Shselasky struct ugold_softc *sc = device_get_softc(dev); 194284272Shselasky struct usb_attach_arg *uaa = device_get_ivars(dev); 195284272Shselasky struct sysctl_oid *sensor_tree; 196284272Shselasky uint16_t d_len; 197284272Shselasky void *d_ptr; 198284272Shselasky int error; 199284272Shselasky int i; 200284272Shselasky 201284272Shselasky sc->sc_udev = uaa->device; 202284272Shselasky sc->sc_readout_msg[0].hdr.pm_callback = &ugold_readout_msg; 203284272Shselasky sc->sc_readout_msg[0].sc = sc; 204284272Shselasky sc->sc_readout_msg[1].hdr.pm_callback = &ugold_readout_msg; 205284272Shselasky sc->sc_readout_msg[1].sc = sc; 206284272Shselasky sc->sc_iface_index[0] = uaa->info.bIfaceIndex; 207284272Shselasky sc->sc_iface_index[1] = uaa->info.bIfaceIndex + 1; 208284272Shselasky 209284272Shselasky device_set_usb_desc(dev); 210284272Shselasky mtx_init(&sc->sc_mtx, "ugold lock", NULL, MTX_DEF | MTX_RECURSE); 211284272Shselasky callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); 212284272Shselasky 213284272Shselasky /* grab all interfaces from other drivers */ 214284272Shselasky for (i = 0;; i++) { 215284272Shselasky if (i == uaa->info.bIfaceIndex) 216284272Shselasky continue; 217284272Shselasky if (usbd_get_iface(uaa->device, i) == NULL) 218284272Shselasky break; 219284272Shselasky 220284272Shselasky usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); 221284272Shselasky } 222284272Shselasky 223284272Shselasky /* figure out report ID */ 224284272Shselasky error = usbd_req_get_hid_desc(uaa->device, NULL, 225284272Shselasky &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); 226284272Shselasky 227284272Shselasky if (error) 228284272Shselasky goto detach; 229284272Shselasky 230284272Shselasky (void)hid_report_size(d_ptr, d_len, hid_input, &sc->sc_report_id); 231284272Shselasky 232284272Shselasky free(d_ptr, M_TEMP); 233284272Shselasky 234284272Shselasky error = usbd_transfer_setup(uaa->device, 235284272Shselasky sc->sc_iface_index, sc->sc_xfer, ugold_config, 236284272Shselasky UGOLD_N_TRANSFER, sc, &sc->sc_mtx); 237284272Shselasky if (error) 238284272Shselasky goto detach; 239284272Shselasky 240284272Shselasky sensor_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), 241284272Shselasky SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors", 242284272Shselasky CTLFLAG_RD, NULL, ""); 243284272Shselasky 244284272Shselasky if (sensor_tree == NULL) { 245284272Shselasky error = ENOMEM; 246284272Shselasky goto detach; 247284272Shselasky } 248284272Shselasky SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), 249284272Shselasky SYSCTL_CHILDREN(sensor_tree), 250284272Shselasky OID_AUTO, "inner", CTLFLAG_RD, &sc->sc_sensor[UGOLD_INNER], 0, 251284272Shselasky "Inner temperature in microCelcius"); 252284272Shselasky 253284272Shselasky SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), 254284272Shselasky SYSCTL_CHILDREN(sensor_tree), 255284272Shselasky OID_AUTO, "inner_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_INNER], 0, 256284272Shselasky "Inner temperature is valid"); 257284272Shselasky 258284272Shselasky SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), 259284272Shselasky SYSCTL_CHILDREN(sensor_tree), 260284272Shselasky OID_AUTO, "inner_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_INNER], 0, 261284272Shselasky "Inner calibration temperature in microCelcius"); 262284272Shselasky 263284272Shselasky SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), 264284272Shselasky SYSCTL_CHILDREN(sensor_tree), 265284272Shselasky OID_AUTO, "outer", CTLFLAG_RD, &sc->sc_sensor[UGOLD_OUTER], 0, 266284272Shselasky "Outer temperature in microCelcius"); 267284272Shselasky 268284272Shselasky SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), 269284272Shselasky SYSCTL_CHILDREN(sensor_tree), 270284272Shselasky OID_AUTO, "outer_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_OUTER], 0, 271284272Shselasky "Outer calibration temperature in microCelcius"); 272284272Shselasky 273284272Shselasky SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), 274284272Shselasky SYSCTL_CHILDREN(sensor_tree), 275284272Shselasky OID_AUTO, "outer_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_OUTER], 0, 276284272Shselasky "Outer temperature is valid"); 277284272Shselasky 278284272Shselasky mtx_lock(&sc->sc_mtx); 279284272Shselasky usbd_transfer_start(sc->sc_xfer[UGOLD_INTR_DT]); 280284272Shselasky ugold_timeout(sc); 281284272Shselasky mtx_unlock(&sc->sc_mtx); 282284272Shselasky 283284272Shselasky return (0); 284284272Shselasky 285284272Shselaskydetach: 286284272Shselasky DPRINTF("error=%s\n", usbd_errstr(error)); 287284272Shselasky ugold_detach(dev); 288284272Shselasky return (error); 289284272Shselasky} 290284272Shselasky 291284272Shselaskystatic int 292284272Shselaskyugold_detach(device_t dev) 293284272Shselasky{ 294284272Shselasky struct ugold_softc *sc = device_get_softc(dev); 295284272Shselasky 296284272Shselasky callout_drain(&sc->sc_callout); 297284272Shselasky 298284272Shselasky usb_proc_explore_lock(sc->sc_udev); 299284272Shselasky usb_proc_explore_mwait(sc->sc_udev, 300284272Shselasky &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]); 301284272Shselasky usb_proc_explore_unlock(sc->sc_udev); 302284272Shselasky 303284272Shselasky usbd_transfer_unsetup(sc->sc_xfer, UGOLD_N_TRANSFER); 304284272Shselasky 305284272Shselasky mtx_destroy(&sc->sc_mtx); 306284272Shselasky 307284272Shselasky return (0); 308284272Shselasky} 309284272Shselasky 310284272Shselaskystatic int 311284272Shselaskyugold_ds75_temp(uint8_t msb, uint8_t lsb) 312284272Shselasky{ 313284272Shselasky /* DS75: 12bit precision mode : 0.0625 degrees Celsius ticks */ 314284272Shselasky /* NOTE: MSB has a sign bit for negative temperatures */ 315284272Shselasky int32_t temp = (msb << 24) | ((lsb & 0xF0) << 16); 316284272Shselasky return (((int64_t)temp * (int64_t)1000000LL) >> 24); 317284272Shselasky} 318284272Shselasky 319284272Shselasky 320284272Shselaskystatic void 321284272Shselaskyugold_intr_callback(struct usb_xfer *xfer, usb_error_t error) 322284272Shselasky{ 323284272Shselasky struct ugold_softc *sc = usbd_xfer_softc(xfer); 324284272Shselasky struct usb_page_cache *pc; 325284272Shselasky uint8_t buf[8]; 326284272Shselasky int temp; 327284272Shselasky int len; 328284272Shselasky 329284272Shselasky usbd_xfer_status(xfer, &len, NULL, NULL, NULL); 330284272Shselasky 331284272Shselasky switch (USB_GET_STATE(xfer)) { 332284272Shselasky case USB_ST_TRANSFERRED: 333284272Shselasky memset(buf, 0, sizeof(buf)); 334284272Shselasky 335284272Shselasky pc = usbd_xfer_get_frame(xfer, 0); 336284272Shselasky usbd_copy_out(pc, 0, buf, MIN(len, sizeof(buf))); 337284272Shselasky 338284272Shselasky switch (buf[0]) { 339284272Shselasky case UGOLD_CMD_INIT: 340284272Shselasky if (sc->sc_num_sensors) 341284272Shselasky break; 342284272Shselasky 343284272Shselasky sc->sc_num_sensors = MIN(buf[1], UGOLD_MAX_SENSORS) /* XXX */ ; 344284272Shselasky 345284272Shselasky DPRINTF("%d sensor%s type ds75/12bit (temperature)\n", 346284272Shselasky sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s"); 347284272Shselasky break; 348284272Shselasky case UGOLD_CMD_DATA: 349284272Shselasky switch (buf[1]) { 350284272Shselasky case 4: 351284272Shselasky temp = ugold_ds75_temp(buf[4], buf[5]); 352284272Shselasky sc->sc_sensor[UGOLD_OUTER] = temp + sc->sc_calib[UGOLD_OUTER]; 353284272Shselasky sc->sc_valid[UGOLD_OUTER] = 1; 354284272Shselasky /* FALLTHROUGH */ 355284272Shselasky case 2: 356284272Shselasky temp = ugold_ds75_temp(buf[2], buf[3]); 357284272Shselasky sc->sc_sensor[UGOLD_INNER] = temp + sc->sc_calib[UGOLD_INNER]; 358284272Shselasky sc->sc_valid[UGOLD_INNER] = 1; 359284272Shselasky break; 360284272Shselasky default: 361284272Shselasky DPRINTF("invalid data length (%d bytes)\n", buf[1]); 362284272Shselasky } 363284272Shselasky break; 364284272Shselasky default: 365284272Shselasky DPRINTF("unknown command 0x%02x\n", buf[0]); 366284272Shselasky break; 367284272Shselasky } 368284272Shselasky /* FALLTHROUGH */ 369284272Shselasky case USB_ST_SETUP: 370284272Shselaskytr_setup: 371284272Shselasky usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); 372284272Shselasky usbd_transfer_submit(xfer); 373284272Shselasky break; 374284272Shselasky default: /* Error */ 375284272Shselasky if (error != USB_ERR_CANCELLED) { 376284272Shselasky /* try clear stall first */ 377284272Shselasky usbd_xfer_set_stall(xfer); 378284272Shselasky goto tr_setup; 379284272Shselasky } 380284272Shselasky break; 381284272Shselasky } 382284272Shselasky} 383284272Shselasky 384284272Shselaskystatic int 385284272Shselaskyugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len) 386284272Shselasky{ 387284272Shselasky return (usbd_req_set_report(sc->sc_udev, &sc->sc_mtx, cmd, len, 388284272Shselasky sc->sc_iface_index[1], UHID_OUTPUT_REPORT, sc->sc_report_id)); 389284272Shselasky} 390284272Shselasky 391284272Shselaskystatic void 392284272Shselaskyugold_readout_msg(struct usb_proc_msg *pm) 393284272Shselasky{ 394284272Shselasky struct ugold_softc *sc = ((struct ugold_readout_msg *)pm)->sc; 395284272Shselasky 396284272Shselasky usb_proc_explore_unlock(sc->sc_udev); 397284272Shselasky 398284272Shselasky mtx_lock(&sc->sc_mtx); 399284272Shselasky if (sc->sc_num_sensors == 0) 400284272Shselasky ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init)); 401284272Shselasky 402284272Shselasky ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data)); 403284272Shselasky mtx_unlock(&sc->sc_mtx); 404284272Shselasky 405284272Shselasky usb_proc_explore_lock(sc->sc_udev); 406284272Shselasky} 407