mirror of https://github.com/nirenjan/libx52.git
326 lines
8.7 KiB
C
326 lines
8.7 KiB
C
/*
|
|
* Saitek X52 Pro MFD & LED driver
|
|
*
|
|
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
|
*
|
|
* 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, version 2.
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "libx52.h"
|
|
#include "x52_commands.h"
|
|
#include "x52_common.h"
|
|
|
|
/* Translate a libusb error to a libx52 error */
|
|
static int libx52_translate_libusb_error(enum libusb_error errcode)
|
|
{
|
|
switch (errcode) {
|
|
case LIBUSB_SUCCESS:
|
|
return LIBX52_SUCCESS;
|
|
|
|
case LIBUSB_ERROR_IO:
|
|
return LIBX52_ERROR_IO;
|
|
|
|
case LIBUSB_ERROR_INVALID_PARAM:
|
|
return LIBX52_ERROR_INVALID_PARAM;
|
|
|
|
case LIBUSB_ERROR_ACCESS:
|
|
return LIBX52_ERROR_PERM;
|
|
|
|
case LIBUSB_ERROR_NO_DEVICE:
|
|
return LIBX52_ERROR_NO_DEVICE;
|
|
|
|
case LIBUSB_ERROR_NOT_FOUND:
|
|
return LIBX52_ERROR_NOT_FOUND;
|
|
|
|
case LIBUSB_ERROR_BUSY:
|
|
return LIBX52_ERROR_BUSY;
|
|
|
|
case LIBUSB_ERROR_TIMEOUT:
|
|
return LIBX52_ERROR_TIMEOUT;
|
|
|
|
case LIBUSB_ERROR_OVERFLOW:
|
|
return LIBX52_ERROR_OVERFLOW;
|
|
|
|
case LIBUSB_ERROR_PIPE:
|
|
return LIBX52_ERROR_PIPE;
|
|
|
|
case LIBUSB_ERROR_INTERRUPTED:
|
|
return LIBX52_ERROR_INTERRUPTED;
|
|
|
|
case LIBUSB_ERROR_NO_MEM:
|
|
return LIBX52_ERROR_OUT_OF_MEMORY;
|
|
|
|
case LIBUSB_ERROR_NOT_SUPPORTED:
|
|
return LIBX52_ERROR_NOT_SUPPORTED;
|
|
|
|
case LIBUSB_ERROR_OTHER:
|
|
default:
|
|
return LIBX52_ERROR_USB_FAILURE;
|
|
};
|
|
}
|
|
|
|
int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value)
|
|
{
|
|
int j;
|
|
int rc = 0;
|
|
|
|
/* Allow retry in case of failure */
|
|
for (j = 0; j < 3; j++) {
|
|
rc = libusb_control_transfer(x52->hdl,
|
|
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT,
|
|
X52_VENDOR_REQUEST, value, index, NULL, 0, 5000);
|
|
|
|
if (rc == LIBUSB_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc == LIBUSB_SUCCESS) {
|
|
return LIBX52_SUCCESS;
|
|
} else {
|
|
return libx52_translate_libusb_error(rc);
|
|
}
|
|
}
|
|
|
|
static int libx52_write_line(libx52_device *x52, uint8_t line_index)
|
|
{
|
|
uint8_t i;
|
|
uint16_t value;
|
|
int rc;
|
|
|
|
const uint16_t line_index_map[X52_MFD_LINES] = {
|
|
X52_MFD_LINE1,
|
|
X52_MFD_LINE2,
|
|
X52_MFD_LINE3,
|
|
};
|
|
|
|
/* Clear the line first */
|
|
rc = libx52_vendor_command(x52,
|
|
line_index_map[line_index] | X52_MFD_CLEAR_LINE, 0);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < x52->line[line_index].length; i += 2) {
|
|
value = x52->line[line_index].text[i + 1] << 8 |
|
|
x52->line[line_index].text[i];
|
|
|
|
rc = libx52_vendor_command(x52,
|
|
line_index_map[line_index] | X52_MFD_WRITE_LINE, value);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int libx52_write_date(libx52_device *x52)
|
|
{
|
|
uint16_t value1; //dd-mm
|
|
uint16_t value2; //yy
|
|
int rc;
|
|
|
|
switch (x52->date_format) {
|
|
case LIBX52_DATE_FORMAT_YYMMDD:
|
|
value1 = x52->date_month << 8 |
|
|
x52->date_year;
|
|
value2 = x52->date_day;
|
|
break;
|
|
|
|
case LIBX52_DATE_FORMAT_MMDDYY:
|
|
value1 = x52->date_day << 8 |
|
|
x52->date_month;
|
|
value2 = x52->date_year;
|
|
break;
|
|
|
|
case LIBX52_DATE_FORMAT_DDMMYY:
|
|
value1 = x52->date_month << 8 |
|
|
x52->date_day;
|
|
value2 = x52->date_year;
|
|
break;
|
|
|
|
default:
|
|
return LIBX52_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
rc = libx52_vendor_command(x52, X52_DATE_DDMM, value1);
|
|
if (rc == LIBX52_SUCCESS) {
|
|
rc = libx52_vendor_command(x52, X52_DATE_YEAR, value2);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int libx52_write_time(libx52_device *x52, libx52_clock_id clock)
|
|
{
|
|
uint16_t value = 0;
|
|
uint16_t index;
|
|
int offset;
|
|
int negative;
|
|
uint16_t h24 = !!(x52->time_format[clock]);
|
|
|
|
if (clock != LIBX52_CLOCK_1) {
|
|
offset = x52->timezone[clock] - x52->timezone[LIBX52_CLOCK_1];
|
|
/* Because the packet format limits the offset from the base clock to
|
|
* a maximum of +/- 1023, we can only handle a maximum offset of 17
|
|
* hours from the base clock. Eg. Honolulu and Auckland at -1000 and
|
|
* +1200 respectively have a direct offset of +22 hours. A potentially
|
|
* greater issue is to take the two extreme timezones at -1200 and +1400
|
|
* for a difference of +26 hours. The safe bet is to check if the offset
|
|
* exceeds +/- 1023, and subtract 1440, which will convert the offset of
|
|
* +22 to -2 hours, or +26 to +4 hours.
|
|
*/
|
|
if (offset < -1023 || offset > 1023) {
|
|
offset -= 1440; // Subtract 24 hours
|
|
}
|
|
|
|
if (offset < 0) {
|
|
negative = 1;
|
|
offset = -offset;
|
|
} else {
|
|
negative = 0;
|
|
}
|
|
|
|
value = h24 << 15 |
|
|
negative << 10 |
|
|
(offset & 0x3FF);
|
|
}
|
|
|
|
switch (clock) {
|
|
case LIBX52_CLOCK_1:
|
|
index = X52_TIME_CLOCK1;
|
|
value = h24 << 15 |
|
|
(x52->time_hour & 0x7F) << 8 |
|
|
(x52->time_minute & 0xFF);
|
|
break;
|
|
|
|
case LIBX52_CLOCK_2:
|
|
index = X52_OFFS_CLOCK2;
|
|
break;
|
|
|
|
case LIBX52_CLOCK_3:
|
|
index = X52_OFFS_CLOCK3;
|
|
break;
|
|
}
|
|
|
|
return libx52_vendor_command(x52, index, value);
|
|
}
|
|
|
|
int libx52_update(libx52_device *x52)
|
|
{
|
|
unsigned int i;
|
|
uint32_t update_mask;
|
|
uint16_t value;
|
|
int rc = LIBX52_SUCCESS;
|
|
|
|
/* Save the update mask */
|
|
update_mask = x52->update_mask;
|
|
/* Reset the device update mask to 0 */
|
|
x52->update_mask = 0;
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (tst_bit(&update_mask, i)) {
|
|
switch (i) {
|
|
case X52_BIT_SHIFT:
|
|
value = tst_bit(&x52->led_mask, X52_BIT_SHIFT) ? X52_SHIFT_ON : X52_SHIFT_OFF;
|
|
rc = libx52_vendor_command(x52, X52_SHIFT_INDICATOR, value);
|
|
break;
|
|
|
|
case X52_BIT_LED_FIRE:
|
|
case X52_BIT_LED_A_RED:
|
|
case X52_BIT_LED_A_GREEN:
|
|
case X52_BIT_LED_B_RED:
|
|
case X52_BIT_LED_B_GREEN:
|
|
case X52_BIT_LED_D_RED:
|
|
case X52_BIT_LED_D_GREEN:
|
|
case X52_BIT_LED_E_RED:
|
|
case X52_BIT_LED_E_GREEN:
|
|
case X52_BIT_LED_T1_RED:
|
|
case X52_BIT_LED_T1_GREEN:
|
|
case X52_BIT_LED_T2_RED:
|
|
case X52_BIT_LED_T2_GREEN:
|
|
case X52_BIT_LED_T3_RED:
|
|
case X52_BIT_LED_T3_GREEN:
|
|
case X52_BIT_LED_POV_RED:
|
|
case X52_BIT_LED_POV_GREEN:
|
|
case X52_BIT_LED_I_RED:
|
|
case X52_BIT_LED_I_GREEN:
|
|
case X52_BIT_LED_THROTTLE:
|
|
/* The bits correspond exactly to the LED identifiers */
|
|
value = tst_bit(&x52->led_mask, i) ? 1 : 0;
|
|
rc = libx52_vendor_command(x52, X52_LED, value | (i << 8));
|
|
break;
|
|
|
|
case X52_BIT_MFD_LINE1:
|
|
rc = libx52_write_line(x52, 0);
|
|
break;
|
|
|
|
case X52_BIT_MFD_LINE2:
|
|
rc = libx52_write_line(x52, 1);
|
|
break;
|
|
|
|
case X52_BIT_MFD_LINE3:
|
|
rc = libx52_write_line(x52, 2);
|
|
break;
|
|
|
|
case X52_BIT_POV_BLINK:
|
|
value = tst_bit(&x52->led_mask, X52_BIT_POV_BLINK)
|
|
? X52_BLINK_ON : X52_BLINK_OFF;
|
|
rc = libx52_vendor_command(x52, X52_BLINK_INDICATOR, value);
|
|
break;
|
|
|
|
case X52_BIT_BRI_MFD:
|
|
rc = libx52_vendor_command(x52, X52_MFD_BRIGHTNESS,
|
|
x52->mfd_brightness);
|
|
break;
|
|
|
|
case X52_BIT_BRI_LED:
|
|
rc = libx52_vendor_command(x52, X52_LED_BRIGHTNESS,
|
|
x52->led_brightness);
|
|
break;
|
|
|
|
case X52_BIT_MFD_DATE:
|
|
rc = libx52_write_date(x52);
|
|
break;
|
|
|
|
case X52_BIT_MFD_TIME:
|
|
rc = libx52_write_time(x52, LIBX52_CLOCK_1);
|
|
break;
|
|
|
|
case X52_BIT_MFD_OFFS1:
|
|
rc = libx52_write_time(x52, LIBX52_CLOCK_2);
|
|
break;
|
|
|
|
case X52_BIT_MFD_OFFS2:
|
|
rc = libx52_write_time(x52, LIBX52_CLOCK_3);
|
|
break;
|
|
|
|
default:
|
|
/* Ignore any spurious bits */
|
|
rc = LIBX52_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
if (rc == LIBX52_SUCCESS) {
|
|
clr_bit(&update_mask, i);
|
|
} else {
|
|
/* Last transfer failed - reset the update mask */
|
|
x52->update_mask = update_mask;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|