/* * Lowlevel hardware access for the * Razer Deathadder mouse * * Important notice: * This hardware driver is based on reverse engineering and * hardware documentation provided under NDA. * * Copyright (C) 2007-2011 Michael Buesch * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "hw_deathadder.h" #include "razer_private.h" #include "cypress_bootloader.h" #include #include #include #include #include enum deathadder_type { DEATHADDER_CLASSIC, /* DeathAdder Classic */ DEATHADDER_3500, /* DeathAdder 3500DPI */ DEATHADDER_BLACK, /* DeathAdder Black Edition */ }; enum { DEATHADDER_LED_SCROLL = 0, DEATHADDER_LED_LOGO, DEATHADDER_NR_LEDS, }; /* This is the device configuration structure sent to the device * for firmware version >= 1.25 */ struct deathadder_125_cfg { uint8_t freq; uint8_t res; uint8_t profile; uint8_t leds; } _packed; struct deathadder_private { struct razer_mouse *m; /* The deathadder hardware revision type */ enum deathadder_type type; bool in_bootloader; /* Firmware version number. */ uint16_t fw_version; /* The currently set LED states. */ bool led_states[DEATHADDER_NR_LEDS]; /* The currently set frequency. */ enum razer_mouse_freq frequency; /* Previous freq. For predicting reconnect events only. */ enum razer_mouse_freq old_frequency; /* The currently set resolution. */ struct razer_mouse_dpimapping *cur_dpimapping; struct razer_mouse_profile profile; struct razer_mouse_dpimapping dpimapping[4]; bool commit_pending; struct razer_event_spacing commit_spacing; }; #define DADD_FW(major, minor) (((major) << 8) | (minor)) #define DEATHADDER_FW_IMAGE_SIZE 0x4000 static int deathadder_usb_write(struct deathadder_private *priv, int request, int command, void *buf, size_t size) { int err; if (priv->in_bootloader) { /* Deathadder firmware is down and we're in the bootloader. */ return 0; } err = libusb_control_transfer( priv->m->usb_ctx->h, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE, request, command, 0, (unsigned char *)buf, size, RAZER_USB_TIMEOUT); if (err < 0 || (size_t)err != size) { razer_error("razer-deathadder: " "USB write 0x%02X 0x%02X failed: %d\n", request, command, err); return err; } return 0; } static int deathadder_usb_read(struct deathadder_private *priv, int request, int command, void *buf, size_t size) { int err; if (priv->in_bootloader) { /* Deathadder firmware is down and we're in the bootloader. */ memset(buf, 0, size); return 0; } err = libusb_control_transfer( priv->m->usb_ctx->h, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE, request, command, 0, buf, size, RAZER_USB_TIMEOUT); if (err < 0 || (size_t)err != size) { razer_error("razer-deathadder: " "USB read 0x%02X 0x%02X failed: %d\n", request, command, err); return err; } return 0; } static int deathadder_read_fw_ver(struct deathadder_private *priv) { char buf[2]; uint16_t ver; int err; err = deathadder_usb_read(priv, LIBUSB_REQUEST_CLEAR_FEATURE, 0x05, buf, sizeof(buf)); if (err) return err; ver = buf[0]; ver <<= 8; ver |= buf[1]; return ver; } static int deathadder_do_commit(struct deathadder_private *priv) { struct razer_usb_reconnect_guard guard; int i, err; if (priv->in_bootloader) return 0; razer_event_spacing_enter(&priv->commit_spacing); err = razer_usb_reconnect_guard_init(&guard, priv->m->usb_ctx); if (err) goto out; if (priv->type == DEATHADDER_CLASSIC && priv->fw_version < DADD_FW(1,25)) { char value, freq_value, res_value; /* Translate frequency setting. */ switch (priv->frequency) { case RAZER_MOUSE_FREQ_125HZ: freq_value = 3; break; case RAZER_MOUSE_FREQ_500HZ: freq_value = 2; break; case RAZER_MOUSE_FREQ_1000HZ: case RAZER_MOUSE_FREQ_UNKNOWN: freq_value = 1; break; default: err = -EINVAL; goto out; } /* Translate resolution setting. */ switch (priv->cur_dpimapping->res[RAZER_DIM_0]) { case RAZER_MOUSE_RES_450DPI: res_value = 3; break; case RAZER_MOUSE_RES_900DPI: res_value = 2; break; case RAZER_MOUSE_RES_1800DPI: case RAZER_MOUSE_RES_UNKNOWN: res_value = 1; break; default: err = -EINVAL; goto out; } if (priv->old_frequency != priv->frequency) { /* Commit frequency setting. */ err = deathadder_usb_write(priv, LIBUSB_REQUEST_SET_CONFIGURATION, 0x07, &freq_value, sizeof(freq_value)); if (err) goto out; /* The frequency setting changed. The device firmware * will reboot the mouse now. This will cause a reconnect * on the USB bus. Call the guard... */ err = razer_usb_reconnect_guard_wait(&guard, 0); if (err) goto out; /* The device needs a bit of punching in the face after reconnect. */ for (i = 0; i < 5; i++) { int ver = deathadder_read_fw_ver(priv); if ((ver > 0) && ((ver & 0xFFFF) == priv->fw_version)) break; razer_msleep(100); } if (i >= 5) { razer_error("razer-deathadder: The device didn't wake up " "after a frequency change. Try to replug it.\n"); } } /* Commit LED states. */ value = 0; if (priv->led_states[DEATHADDER_LED_LOGO]) value |= 0x01; if (priv->led_states[DEATHADDER_LED_SCROLL]) value |= 0x02; err = deathadder_usb_write(priv, LIBUSB_REQUEST_SET_CONFIGURATION, 0x06, &value, sizeof(value)); if (err) goto out; /* Commit resolution setting. */ err = deathadder_usb_write(priv, LIBUSB_REQUEST_SET_CONFIGURATION, 0x09, &res_value, sizeof(res_value)); if (err) goto out; } else { struct deathadder_125_cfg config = { 0, }; /* Translate frequency setting. */ switch (priv->frequency) { case RAZER_MOUSE_FREQ_125HZ: config.freq = 3; break; case RAZER_MOUSE_FREQ_500HZ: config.freq = 2; break; case RAZER_MOUSE_FREQ_1000HZ: case RAZER_MOUSE_FREQ_UNKNOWN: config.freq = 1; break; default: err = -EINVAL; goto out; } /* Translate resolution setting. */ if (priv->type == DEATHADDER_CLASSIC) { switch (priv->cur_dpimapping->res[RAZER_DIM_0]) { case RAZER_MOUSE_RES_450DPI: config.res = 3; break; case RAZER_MOUSE_RES_900DPI: config.res = 2; break; case RAZER_MOUSE_RES_1800DPI: case RAZER_MOUSE_RES_UNKNOWN: config.res = 1; break; default: err = -EINVAL; goto out; } } else { switch (priv->cur_dpimapping->res[RAZER_DIM_0]) { case RAZER_MOUSE_RES_450DPI: config.res = 4; break; case RAZER_MOUSE_RES_900DPI: config.res = 3; break; case RAZER_MOUSE_RES_1800DPI: config.res = 2; break; case RAZER_MOUSE_RES_3500DPI: case RAZER_MOUSE_RES_UNKNOWN: config.res = 1; break; default: err = -EINVAL; goto out; } } /* The profile ID. */ config.profile = 1; /* Translate the LED states. */ if (priv->type == DEATHADDER_BLACK) { /* There are no LEDs. * Bit 0 and 1 are always set, though. */ config.leds = 0x03; } else { if (priv->led_states[DEATHADDER_LED_LOGO]) config.leds |= 0x01; if (priv->led_states[DEATHADDER_LED_SCROLL]) config.leds |= 0x02; } /* Commit the settings. */ err = deathadder_usb_write(priv, LIBUSB_REQUEST_SET_CONFIGURATION, 0x10, &config, sizeof(config)); if (err) goto out; if ((priv->type == DEATHADDER_CLASSIC || priv->type == DEATHADDER_3500) && priv->frequency != priv->old_frequency) { /* The frequency setting changed. The device firmware * will reboot the mouse now. This will cause a reconnect * on the USB bus. Call the guard... */ err = razer_usb_reconnect_guard_wait(&guard, 0); if (err) goto out; /* The device has reconnected, so write the config * another time to ensure all settings are active. */ err = deathadder_usb_write(priv, LIBUSB_REQUEST_SET_CONFIGURATION, 0x10, &config, sizeof(config)); if (err) goto out; } if (priv->type == DEATHADDER_CLASSIC) { /* The device needs a bit of punching in the face. * Ensure it properly responds to read accesses. */ for (i = 0; i < 5; i++) { int ver = deathadder_read_fw_ver(priv); if ((ver > 0) && ((ver & 0xFFFF) == priv->fw_version)) break; razer_msleep(100); } if (i >= 5) { razer_error("razer-deathadder: The device didn't wake up " "after a config change. Try to replug it.\n"); } } } priv->old_frequency = priv->frequency; err = 0; out: razer_event_spacing_leave(&priv->commit_spacing); return err; } static int deathadder_get_fw_version(struct razer_mouse *m) { struct deathadder_private *priv = m->drv_data; return priv->fw_version; } static int deathadder_commit(struct razer_mouse *m, int force) { struct deathadder_private *priv = m->drv_data; int err = 0; if (!m->claim_count) return -EBUSY; if (priv->commit_pending || force) { err = deathadder_do_commit(priv); if (!err) priv->commit_pending = 0; } return err; } static int deathadder_led_toggle(struct razer_led *led, enum razer_led_state new_state) { struct razer_mouse *m = led->u.mouse; struct deathadder_private *priv = m->drv_data; if (led->id >= DEATHADDER_NR_LEDS) return -EINVAL; if ((new_state != RAZER_LED_OFF) && (new_state != RAZER_LED_ON)) return -EINVAL; if (priv->type == DEATHADDER_BLACK) return -ENODEV; if (!m->claim_count) return -EBUSY; priv->led_states[led->id] = new_state; priv->commit_pending = 1; return 0; } static int deathadder_get_leds(struct razer_mouse *m, struct razer_led **leds_list) { struct deathadder_private *priv = m->drv_data; struct razer_led *scroll, *logo; if (priv->type == DEATHADDER_BLACK) return 0; /* No LEDs */ scroll = zalloc(sizeof(struct razer_led)); if (!scroll) return -ENOMEM; logo = zalloc(sizeof(struct razer_led)); if (!logo) { free(scroll); return -ENOMEM; } scroll->name = "Scrollwheel"; scroll->id = DEATHADDER_LED_SCROLL; scroll->state = priv->led_states[DEATHADDER_LED_SCROLL]; scroll->toggle_state = deathadder_led_toggle; scroll->u.mouse = m; logo->name = "GlowingLogo"; logo->id = DEATHADDER_LED_LOGO; logo->state = priv->led_states[DEATHADDER_LED_LOGO]; logo->toggle_state = deathadder_led_toggle; logo->u.mouse = m; /* Link the list */ *leds_list = scroll; scroll->next = logo; logo->next = NULL; return DEATHADDER_NR_LEDS; } static int deathadder_supported_freqs(struct razer_mouse *m, enum razer_mouse_freq **freq_list) { enum razer_mouse_freq *list; const int count = 3; list = zalloc(sizeof(*list) * count); if (!list) return -ENOMEM; list[0] = RAZER_MOUSE_FREQ_125HZ; list[1] = RAZER_MOUSE_FREQ_500HZ; list[2] = RAZER_MOUSE_FREQ_1000HZ; *freq_list = list; return count; } static enum razer_mouse_freq deathadder_get_freq(struct razer_mouse_profile *p) { struct deathadder_private *priv = p->mouse->drv_data; return priv->frequency; } static int deathadder_set_freq(struct razer_mouse_profile *p, enum razer_mouse_freq freq) { struct deathadder_private *priv = p->mouse->drv_data; if (!priv->m->claim_count) return -EBUSY; priv->frequency = freq; priv->commit_pending = 1; return 0; } static int deathadder_supported_resolutions(struct razer_mouse *m, enum razer_mouse_res **res_list) { struct deathadder_private *priv = m->drv_data; enum razer_mouse_res *list; const int count = (priv->type == DEATHADDER_CLASSIC) ? 3 : 4; list = zalloc(sizeof(*list) * count); if (!list) return -ENOMEM; list[0] = RAZER_MOUSE_RES_450DPI; list[1] = RAZER_MOUSE_RES_900DPI; list[2] = RAZER_MOUSE_RES_1800DPI; if (priv->type != DEATHADDER_CLASSIC) list[3] = RAZER_MOUSE_RES_3500DPI; *res_list = list; return count; } //TODO #if 0 static struct usb_device * wait_for_usbdev(struct usb_device *dev, uint16_t vendor_id, uint16_t product_id) { struct usb_bus *bus, *buslist; unsigned int i; if (dev->descriptor.idVendor == vendor_id && dev->descriptor.idProduct == product_id) return dev; for (i = 0; i < 100; i++) { usb_find_busses(); usb_find_devices(); buslist = usb_get_busses(); for_each_usbbus(bus, buslist) { for_each_usbdev(dev, bus->devices) { if (dev->descriptor.idVendor == vendor_id && dev->descriptor.idProduct == product_id) return dev; } } razer_msleep(100); } return NULL; } #endif static int deathadder_flash_firmware(struct razer_mouse *m, const char *data, size_t len, unsigned int magic_number) { struct deathadder_private *priv = m->drv_data; int err; char value; struct libusb_device *cydev; struct cypress cy; if (magic_number != RAZER_FW_FLASH_MAGIC) return -EINVAL; if (!m->claim_count) return -EBUSY; if (len != DEATHADDER_FW_IMAGE_SIZE) { razer_error("razer-deathadder: " "Firmware image has wrong size %u (expected %u).\n", (unsigned int)len, (unsigned int)DEATHADDER_FW_IMAGE_SIZE); return -EINVAL; } razer_msleep(50); if (priv->in_bootloader) { /* We're already inside of the bootloader */ cydev = m->usb_ctx->dev; } else { /* Enter bootloader mode */ value = 0; err = deathadder_usb_write(priv, LIBUSB_REQUEST_SET_CONFIGURATION, 0x08, &value, sizeof(value)); if (err) { razer_error("razer-deathadder: Failed to enter the bootloader.\n"); return err; } /* Wait for the cypress device to appear. */ //TODO cydev = wait_for_usbdev(priv->usb.dev, CYPRESS_BOOT_VENDORID, CYPRESS_BOOT_PRODUCTID); cydev=NULL; if (!cydev) { razer_error("razer-deathadder: Cypress device didn't appear.\n"); return -1; } } razer_msleep(100); err = cypress_open(&cy, cydev, NULL); if (err) return err; err = cypress_upload_image(&cy, data, len); cypress_close(&cy); if (err) return err; //FIXME need to reconnect the device? return 0; } static struct razer_mouse_profile * deathadder_get_profiles(struct razer_mouse *m) { struct deathadder_private *priv = m->drv_data; return &priv->profile; } static int deathadder_supported_dpimappings(struct razer_mouse *m, struct razer_mouse_dpimapping **res_ptr) { struct deathadder_private *priv = m->drv_data; *res_ptr = &priv->dpimapping[0]; if (priv->type == DEATHADDER_CLASSIC) return ARRAY_SIZE(priv->dpimapping) - 1; return ARRAY_SIZE(priv->dpimapping); } static struct razer_mouse_dpimapping * deathadder_get_dpimapping(struct razer_mouse_profile *p, struct razer_axis *axis) { struct deathadder_private *priv = p->mouse->drv_data; return priv->cur_dpimapping; } static int deathadder_set_dpimapping(struct razer_mouse_profile *p, struct razer_axis *axis, struct razer_mouse_dpimapping *d) { struct deathadder_private *priv = p->mouse->drv_data; if (!priv->m->claim_count) return -EBUSY; priv->cur_dpimapping = d; priv->commit_pending = 1; return 0; } int razer_deathadder_init(struct razer_mouse *m, struct libusb_device *usbdev) { struct deathadder_private *priv; struct libusb_device_descriptor desc; unsigned int i; int err, fwver; const char *devname = ""; err = libusb_get_device_descriptor(usbdev, &desc); if (err) { razer_error("hw_deathadder: Failed to get device descriptor\n"); return -EIO; } priv = zalloc(sizeof(struct deathadder_private)); if (!priv) return -ENOMEM; priv->m = m; m->drv_data = priv; priv->in_bootloader = is_cypress_bootloader(&desc); /* We need to wait some time between commits */ razer_event_spacing_init(&priv->commit_spacing, 1000); err = razer_usb_add_used_interface(m->usb_ctx, 0, 0); if (err) goto err_free; if (!priv->in_bootloader && desc.idProduct == 0x0007) { err = razer_usb_force_hub_reset(m->usb_ctx); if (err) { razer_error("hw_deathadder: Failed to reinit USB device\n"); goto err_free; } usbdev = m->usb_ctx->dev; } err = m->claim(m); if (err) { razer_error("hw_deathadder: Failed to claim device\n"); goto err_free; } /* Fetch firmware version */ fwver = deathadder_read_fw_ver(priv); if (fwver < 0) { razer_error("hw_deathadder: Failed to get firmware version\n"); err = fwver; goto err_release; } priv->fw_version = fwver; /* Determine the hardware revision */ priv->type = DEATHADDER_CLASSIC; if (desc.idVendor == 0x1532 && desc.idProduct == 0x0029) { priv->type = DEATHADDER_BLACK; } else { if (fwver >= DADD_FW(2,0)) priv->type = DEATHADDER_3500; } priv->frequency = RAZER_MOUSE_FREQ_1000HZ; priv->old_frequency = priv->frequency; for (i = 0; i < DEATHADDER_NR_LEDS; i++) priv->led_states[i] = RAZER_LED_ON; priv->profile.nr = 0; priv->profile.get_freq = deathadder_get_freq; priv->profile.set_freq = deathadder_set_freq; priv->profile.get_dpimapping = deathadder_get_dpimapping; priv->profile.set_dpimapping = deathadder_set_dpimapping; priv->profile.mouse = m; priv->dpimapping[0].nr = 0; priv->dpimapping[0].res[RAZER_DIM_0] = RAZER_MOUSE_RES_450DPI; priv->dpimapping[0].dimension_mask = (1 << RAZER_DIM_0); priv->dpimapping[0].change = NULL; priv->dpimapping[0].mouse = m; priv->dpimapping[1].nr = 1; priv->dpimapping[1].res[RAZER_DIM_0] = RAZER_MOUSE_RES_900DPI; priv->dpimapping[1].dimension_mask = (1 << RAZER_DIM_0); priv->dpimapping[1].change = NULL; priv->dpimapping[1].mouse = m; priv->dpimapping[2].nr = 2; priv->dpimapping[2].res[RAZER_DIM_0] = RAZER_MOUSE_RES_1800DPI; priv->dpimapping[2].dimension_mask = (1 << RAZER_DIM_0); priv->dpimapping[2].change = NULL; priv->dpimapping[2].mouse = m; if (priv->type == DEATHADDER_CLASSIC) { priv->cur_dpimapping = &priv->dpimapping[2]; } else { priv->dpimapping[3].nr = 3; priv->dpimapping[3].res[RAZER_DIM_0] = RAZER_MOUSE_RES_3500DPI; priv->dpimapping[3].dimension_mask = (1 << RAZER_DIM_0); priv->dpimapping[3].change = NULL; priv->dpimapping[3].mouse = m; priv->cur_dpimapping = &priv->dpimapping[3]; } m->type = RAZER_MOUSETYPE_DEATHADDER; switch (priv->type) { case DEATHADDER_CLASSIC: devname = "DeathAdder Classic"; break; case DEATHADDER_3500: devname = "DeathAdder 3500DPI"; break; case DEATHADDER_BLACK: devname = "DeathAdder Black Edition"; break; } razer_generic_usb_gen_idstr(usbdev, m->usb_ctx->h, devname, 0, NULL, m->idstr); m->get_fw_version = deathadder_get_fw_version; m->commit = deathadder_commit; m->flash_firmware = deathadder_flash_firmware; m->global_get_leds = deathadder_get_leds; m->get_profiles = deathadder_get_profiles; m->supported_resolutions = deathadder_supported_resolutions; m->supported_freqs = deathadder_supported_freqs; m->supported_dpimappings = deathadder_supported_dpimappings; err = deathadder_do_commit(priv); if (err) { razer_error("hw_deathadder: Failed to commit initial settings\n"); goto err_release; } m->release(m); return 0; err_release: m->release(m); err_free: free(priv); return err; } void razer_deathadder_release(struct razer_mouse *m) { struct deathadder_private *priv = m->drv_data; free(priv); }