581 lines
15 KiB
C
581 lines
15 KiB
C
|
|
/* Abstract Video Test Patch Generator and/or 3dLUT class implemenation */
|
|
|
|
/*
|
|
* Argyll Color Management System
|
|
*
|
|
* Author: Graeme W. Gill
|
|
* Date: 10/3/2001
|
|
*
|
|
* Copyright 2001 - 2013 Graeme W. Gill
|
|
* All rights reserved.
|
|
*
|
|
* This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
|
|
* see the License2.txt file for licencing details.
|
|
*/
|
|
|
|
/*
|
|
TTBD:
|
|
|
|
Note that there is some support for HW 3dLUTs in X11 xrandr
|
|
in a patch for xf86-videeo-amdgpu drmmode_display.c
|
|
submitted on 3 May 2018.
|
|
See <https://lists.freedesktop.org/archives/amd-gfx/2018-May/022007.html>
|
|
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#ifndef SALONEINSTLIB
|
|
#include "copyright.h"
|
|
#include "aconfig.h"
|
|
#else
|
|
#include "sa_config.h"
|
|
#endif /* !SALONEINSTLIB */
|
|
#include "numsup.h"
|
|
#include "cgats.h"
|
|
#include "xspect.h"
|
|
#include "conv.h"
|
|
|
|
#include "insttypes.h"
|
|
#include "icoms.h"
|
|
#include "vtpglut.h"
|
|
#include "rspec.h"
|
|
#include "vtpgluttypes.h"
|
|
#include "sort.h"
|
|
|
|
icom_type dev_category(instType itype);
|
|
|
|
/* --------------------------------------------------------- */
|
|
|
|
#if defined(ENABLE_FAST_SERIAL)
|
|
extern devType fast_ser_dev_type(icoms *p, int tryhard,
|
|
inst_code (*uicallback)(void *cntx, inst_ui_purp purp), void *cntx);
|
|
# if defined(ENABLE_SERIAL)
|
|
static devType ser_vtpglut_type(icoms *p,
|
|
vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp), void *cntx);
|
|
|
|
/* Translate vtpglut uicallback into inst callback */
|
|
typedef struct {
|
|
void *cntx;
|
|
vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp);
|
|
} vtpglut_callback_cntx;
|
|
|
|
static inst_code ser_uicallback(void *cntx, inst_ui_purp purp) {
|
|
vtpglut_callback_cntx *p = (vtpglut_callback_cntx *)cntx;
|
|
vtpglut_ui_purp ser_purp;
|
|
|
|
if (purp == vtpglut_negcoms)
|
|
ser_purp = inst_negcoms;
|
|
else
|
|
return inst_internal_error;
|
|
return p->uicallback(p->cntx, ser_purp);
|
|
}
|
|
|
|
# endif /* ENABLE_SERIAL */
|
|
#endif /* ENABLE_FAST_SERIAL */
|
|
|
|
|
|
/* ------------------------------------ */
|
|
/* Default methods for instrument class */
|
|
|
|
/* Establish communications at the indicated baud rate. */
|
|
/* Timeout in to seconds, and return non-zero error code */
|
|
static vtpglut_code init_coms(
|
|
vtpglut *p) { /* Timeout */
|
|
return vtpglut_unsupported;
|
|
}
|
|
|
|
/* Initialise or re-initialise the INST */
|
|
/* return non-zero on an error, with inst error code */
|
|
static vtpglut_code init_vtpglut(
|
|
vtpglut *p) {
|
|
return vtpglut_unsupported;
|
|
}
|
|
|
|
/* Return the device type */
|
|
static devType get_dtype(vtpglut *p) {
|
|
if (p != NULL)
|
|
return p->dtype;
|
|
return devUnknown;
|
|
}
|
|
|
|
/* Return the device serial number. */
|
|
/* (This will be an empty string if there is no serial no) */
|
|
static char *get_serial_no(vtpglut *p) {
|
|
return "";
|
|
}
|
|
|
|
/* Return the device mode/capabilities */
|
|
static void capabilities(vtpglut *p, vtpglut_mode *cap1,
|
|
vtpglut_capability *cap2) {
|
|
if (cap1 != NULL)
|
|
*cap1 = vtpglut_mode_none;
|
|
if (cap2 != NULL)
|
|
*cap2 = vtpglut2_none;
|
|
}
|
|
|
|
/* Check the device mode */
|
|
static vtpglut_code check_mode(
|
|
vtpglut *p,
|
|
vtpglut_mode m) { /* Requested mode */
|
|
return vtpglut_unsupported;
|
|
}
|
|
|
|
/* Set the device mode */
|
|
static vtpglut_code set_mode(
|
|
vtpglut *p,
|
|
vtpglut_mode m) { /* Requested mode */
|
|
return vtpglut_unsupported;
|
|
}
|
|
|
|
/* Get a status or set or get an option (default implementation) */
|
|
vtpglut_code vtpglut_get_set_opt_def(
|
|
vtpglut *p,
|
|
vtpglut_opt_type m, /* Requested option type */
|
|
va_list args) { /* Option parameters */
|
|
return vtpglut_unsupported;
|
|
}
|
|
|
|
/* Get a status or set or get an option */
|
|
static vtpglut_code get_set_opt(
|
|
vtpglut *p,
|
|
vtpglut_opt_type m, /* Requested status type */
|
|
...) { /* Status parameters */
|
|
vtpglut_code rv;
|
|
va_list args;
|
|
|
|
va_start(args, m);
|
|
rv = vtpglut_get_set_opt_def(p, m, args); /* Call the above */
|
|
va_end(args);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Supply a user interaction callback function. */
|
|
static void set_uicallback(
|
|
vtpglut *pp,
|
|
vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp),
|
|
void *cntx) {
|
|
pp->uicallback = uicallback;
|
|
pp->uic_cntx = cntx;
|
|
}
|
|
|
|
/* Supply an asynchronous event callback function. */
|
|
static void set_event_callback(
|
|
vtpglut *pp,
|
|
void (*eventcallback)(void *cntx, vtpglut_event_type event),
|
|
void *cntx) {
|
|
pp->eventcallback = eventcallback;
|
|
pp->event_cntx = cntx;
|
|
}
|
|
|
|
/* Generic inst error codes interpretation */
|
|
static char *vtpglut_interp_error(vtpglut *p, vtpglut_code ec) {
|
|
switch(ec & vtpglut_mask) {
|
|
case vtpglut_ok:
|
|
return "No error";
|
|
case vtpglut_notify:
|
|
return "Notification";
|
|
case vtpglut_warning:
|
|
return "Warning";
|
|
case vtpglut_no_coms:
|
|
return "Internal error - communications needed but not established";
|
|
case vtpglut_no_init:
|
|
return "Internal error - initialisation needed but not done";
|
|
case vtpglut_unsupported:
|
|
return "Unsupported function";
|
|
case vtpglut_internal_error:
|
|
return "Internal software error";
|
|
case vtpglut_coms_fail:
|
|
return "Communications failure";
|
|
case vtpglut_unknown_model:
|
|
return "Not expected device model";
|
|
case vtpglut_protocol_error:
|
|
return "Communication protocol breakdown";
|
|
case vtpglut_user_abort:
|
|
return "User hit Abort Key";
|
|
case vtpglut_user_trig:
|
|
return "User hit Trigger Key";
|
|
case vtpglut_unexpected_reply:
|
|
return "Unexpected Reply";
|
|
case vtpglut_wrong_setup:
|
|
return "Wrong or conflicting setup";
|
|
case vtpglut_hardware_fail:
|
|
return "Hardware Failure";
|
|
case vtpglut_system_error:
|
|
return "Operating System Error";
|
|
case vtpglut_bad_parameter:
|
|
return "Bad Parameter Value";
|
|
case vtpglut_other_error:
|
|
return "Non-specific error";
|
|
}
|
|
return "Unknown inst error code";
|
|
}
|
|
|
|
/* Instrument specific error codes interpretation */
|
|
static char *interp_error(vtpglut *p, int ec) {
|
|
return "interp_error is uninplemented in base class!";
|
|
}
|
|
|
|
/* Return the last serial communication error code */
|
|
/* (This is used for deciding fallback/retry strategies) */
|
|
static int last_scomerr(vtpglut *p) {
|
|
return p->icom->lserr;
|
|
}
|
|
|
|
/* ---------------------------------------------- */
|
|
|
|
/* Delete things set/done by new_vtpglut() */
|
|
static vtpglut_code virtual_del(vtpglut *p) {
|
|
|
|
#if defined(UNIX_APPLE)
|
|
osx_latencycritical_end();
|
|
#endif
|
|
|
|
return vtpglut_ok;
|
|
}
|
|
|
|
|
|
/* Virtual constructor. */
|
|
/* Return NULL for unknown instrument, */
|
|
/* or serial instrument if nocoms == 0. */
|
|
extern vtpglut *new_vtpglut(
|
|
icompath *path, /* Device path to instrument */
|
|
int nocoms, /* Don't open if communications are needed to establish inst type */
|
|
a1log *log, /* log to use */
|
|
vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp), /* optional uicallback for abort */
|
|
void *cntx /* Context for callback */
|
|
) {
|
|
devType itype = devUnknown; /* Actual type */
|
|
icoms *icom;
|
|
vtpglut *p = NULL;
|
|
|
|
if (path == NULL) {
|
|
a1logd(log, 2, "new_vtpglut: got NULL path\n");
|
|
return NULL;
|
|
}
|
|
|
|
a1logd(log, 2, "new_vtpglut: called with path '%s' type '%s'\n",path->name,inst_sname(path->itype));
|
|
|
|
if ((icom = new_icoms(path, log)) == NULL) {
|
|
a1logd(log, 2, "new_vtpglut: new_icoms failed to open it\n");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/* Set device type from USB port, if not specified */
|
|
itype = icom->itype; /* Instrument type if its known from usb/hid */
|
|
|
|
#if defined(ENABLE_FAST_SERIAL)
|
|
if (itype == instUnknown && !nocoms && (icom->dctype & icomt_fastserial)) {
|
|
vtpglut_callback_cntx wcntx;
|
|
|
|
/* Wrap vtpglut callback in inst callback */
|
|
wcntx.cntx = cntx;
|
|
wcntx.uicallback = uicallback;
|
|
|
|
itype = fast_ser_dev_type(icom, 1, ser_uicallback, &wcntx); /* Else type from serial */
|
|
icom->dctype = (icom->dctype & ~icomt_cat_mask) | dev_category(itype);
|
|
a1logd(log, 8, "new_vtpglut: fast set '%s' dctype 0x%x\n",icom->name,icom->dctype);
|
|
}
|
|
#endif /* ENABLE_FAST_SERIAL */
|
|
|
|
#if defined(ENABLE_SERIAL)
|
|
if (itype == instUnknown && !nocoms) {
|
|
itype = ser_vtpglut_type(icom, uicallback, cntx); /* Else type from serial */
|
|
icom->dctype = (icom->dctype & ~icomt_cat_mask) | dev_category(itype);
|
|
a1logd(log, 8, "new_vtpglut: set '%s' dctype 0x%x\n",icom->name,icom->dctype);
|
|
}
|
|
#endif /* ENABLE_SERIAL */
|
|
|
|
|
|
#ifdef ENABLE_SERIAL
|
|
// if (itype == devRadiance)
|
|
// p = (vtpglut *)new_radiance(icom, itype);
|
|
#endif /* ENABLE_SERIAL */
|
|
|
|
#ifdef ENABLE_USB
|
|
// if (itype == instXXXX)
|
|
// p = (vtpglut *)new_XXXX(icom, itype);
|
|
#endif /* ENABLE_USB */
|
|
|
|
|
|
/* Nothing matched */
|
|
if (p == NULL) {
|
|
a1logd(log, 2, "new_vtpglut: device type not recognised\n");
|
|
icom->del(icom);
|
|
return NULL;
|
|
}
|
|
|
|
p->vdel = virtual_del;
|
|
|
|
/* Add default methods if constructor did not supply them */
|
|
if (p->init_coms == NULL)
|
|
p->init_coms = init_coms;
|
|
if (p->init_vtpglut == NULL)
|
|
p->init_vtpglut = init_vtpglut;
|
|
if (p->get_dtype == NULL)
|
|
p->get_dtype = get_dtype;
|
|
if (p->get_serial_no == NULL)
|
|
p->get_serial_no = get_serial_no;
|
|
if (p->capabilities == NULL)
|
|
p->capabilities = capabilities;
|
|
if (p->check_mode == NULL)
|
|
p->check_mode = check_mode;
|
|
if (p->set_mode == NULL)
|
|
p->set_mode = set_mode;
|
|
if (p->get_set_opt == NULL)
|
|
p->get_set_opt = get_set_opt;
|
|
if (p->set_uicallback == NULL)
|
|
p->set_uicallback = set_uicallback;
|
|
if (p->set_event_callback == NULL)
|
|
p->set_event_callback = set_event_callback;
|
|
if (p->vtpglut_interp_error == NULL)
|
|
p->vtpglut_interp_error = vtpglut_interp_error;
|
|
if (p->interp_error == NULL)
|
|
p->interp_error = interp_error;
|
|
if (p->last_scomerr == NULL)
|
|
p->last_scomerr = last_scomerr;
|
|
|
|
/* Set the provided user interaction callback */
|
|
p->set_uicallback(p, uicallback, cntx);
|
|
|
|
#if defined(UNIX_APPLE)
|
|
osx_latencycritical_start();
|
|
#endif
|
|
|
|
return p;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* Detect serial device */
|
|
|
|
#ifdef ENABLE_SERIAL
|
|
static void hex2bin(char *buf, int len);
|
|
|
|
/* Heuristicly determine the device type for */
|
|
/* a serial connection, and devUnknown if not serial. */
|
|
/* Set it in icoms and also return it. */
|
|
static devType ser_vtpglut_type(
|
|
icoms *p,
|
|
vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp), /* optional uicallback */
|
|
void *cntx /* Context for callback */
|
|
) {
|
|
devType rv = instUnknown;
|
|
#define BUFSZ (128 + 10)
|
|
char buf[BUFSZ];
|
|
baud_rate brt[] = { baud_9600, baud_57600, baud_115200, baud_230400, baud_nc };
|
|
unsigned int etime;
|
|
unsigned int bi, i;
|
|
int se, len, bread;
|
|
int lumagen = 0, lgchsum = 0;
|
|
|
|
#ifdef ENABLE_USB
|
|
if (p->usbd != NULL || p->hidd != NULL)
|
|
return p->itype;
|
|
#endif /* ENABLE_USB */
|
|
|
|
bi = 0;
|
|
|
|
/* The tick to give up on */
|
|
etime = msec_time() + (long)(20.0 * 1000.0 + 0.5);
|
|
|
|
a1logd(p->log, 1, "ser_vtpglut_type: Trying different baud rates (%u msec to go)\n",etime - msec_time());
|
|
|
|
/* Until we time out, find the correct baud rate */
|
|
for (i = bi; msec_time() < etime; i++) {
|
|
lgchsum = 0;
|
|
|
|
if (brt[i] == baud_nc)
|
|
i = 0;
|
|
if ((se = p->set_ser_port(p, fc_None, brt[i], parity_none,
|
|
stop_1, length_8)) != ICOM_OK) {
|
|
a1logd(p->log, 5, "ser_vtpglut_type: set_ser_port failed with 0x%x\n",se);
|
|
return instUnknown; /* Give up */
|
|
}
|
|
|
|
a1logd(p->log, 5, "Trying %s baud\n",baud_rate_to_str(brt[i]));
|
|
bread = 0;
|
|
|
|
/* If Lumagen baud rate */
|
|
if (brt[i] == baud_9600
|
|
|| brt[i] == baud_57600
|
|
|| brt[i] == baud_115200
|
|
|| brt[i] == baud_230400) {
|
|
|
|
/* Try a spectrolino/spectroscan command first, so as not to upset it, */
|
|
/* but do first character only and see if there is an echo */
|
|
p->write_read_ex(p, ";", 1, buf, BUFSZ-1, &bread, "\r", 1, 0.2, 1);
|
|
if (bread == 1 && buf[0] == ';')
|
|
goto check_lumagen; /* It may be a Lumagen, so skip it. */
|
|
|
|
/* Send the rest of the spectrolino command */
|
|
p->write_read_ex(p, "D024\r\n", 0, buf, BUFSZ-1, &bread, "\r", 1, 0.5, 1);
|
|
|
|
|
|
if (bread == 0) {
|
|
char *bp;
|
|
a1logd(p->log, 5, "ser_vtpglut_type: Spectrolino command returned nothing\n");
|
|
|
|
/* It could be a Lumagen Radiance with echo off, */
|
|
/* so poke it and see if it responds. */
|
|
/* (Unfortunately the Lumagen delimeters modes aren't */
|
|
/* backwards compatible, so we may have to poke it twice...) */
|
|
|
|
/* Send "X" first, to get it out of menu mode ? */
|
|
p->write_read_ex(p, "X", 1, buf, BUFSZ, NULL, "\n", 1, 0.1, 1); // Menu off
|
|
|
|
a1logd(p->log, 5, "ser_vtpglut_type: Checking for Lumagen Radiance\n");
|
|
p->write_read_ex(p, "#ZQS00\r", 0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);
|
|
if (bread == 0) {
|
|
p->write_read_ex(p, "#0ZQS008E\r", 0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);
|
|
lgchsum = 1;
|
|
}
|
|
|
|
if (bread > 0
|
|
&& (bp = strrchr(buf, '!')) != NULL
|
|
&& strlen(bp) >= 4
|
|
&& strncmp(bp,"!S00",4) == 0) {
|
|
goto check_lumagen;
|
|
}
|
|
|
|
/* Nope - look for something at a different baud rate */
|
|
goto continue_looking;
|
|
}
|
|
buf[bread] = '\000';
|
|
len = strlen(buf);
|
|
|
|
/* Is this a Lumagen Radiance with echo on, it responds with ";D024..." */
|
|
if ((len >= 4 && strncmp(buf, ";D024", 4) == 0)
|
|
|| (len >= 4 && strncmp(buf, "!N\n\r", 4) == 0)) {
|
|
char *bp;
|
|
|
|
check_lumagen:;
|
|
|
|
/* Get the Lumagen device information */
|
|
p->write_read_ex(p, lgchsum ? "#0ZQS018F\r" : "#ZQS01\r",
|
|
0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);
|
|
|
|
/* Might have echo with checksum, so lgchsum not set correctly */
|
|
if (!lgchsum && bread > 0 && strstr(buf, "!N") != NULL) {
|
|
p->write_read_ex(p, "#0ZQS018F\r", 0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);
|
|
if (bread >= 11 && strncmp(buf, "#0ZQS018F!Y", 11) == 0)
|
|
lgchsum = 1;
|
|
}
|
|
|
|
/* returns something like "ZQS01!S01,Radiance2020,030115,1016,001309\r\m" */
|
|
if ((bp = strrchr(buf, '!')) != NULL && strlen(bp) >= 13) {
|
|
if (strncmp(bp,"!S01,Radiance",13) == 0) {
|
|
a1logd(p->log, 5, "ser_vtpglut_type: Found Lumagen Radiance\n");
|
|
lumagen = 1;
|
|
break;
|
|
}
|
|
}
|
|
a1logd(p->log, 5, "ser_vtpglut_type: Not Lumagen Radiance\n");
|
|
goto continue_looking;
|
|
}
|
|
} /* Possibly Lumagen */
|
|
|
|
continue_looking:;
|
|
|
|
/* Check for user abort */
|
|
if (uicallback != NULL) {
|
|
vtpglut_code ev;
|
|
if ((ev = uicallback(cntx, vtpglut_negcoms)) == vtpglut_user_abort) {
|
|
a1logd(p->log, 5, "ser_vtpglut_type: User aborted\n");
|
|
return instUnknown;
|
|
}
|
|
}
|
|
} /* next baud */
|
|
|
|
if (rv == instUnknown
|
|
&& lumagen == 0
|
|
&& msec_time() >= etime) { /* We haven't established comms */
|
|
a1logd(p->log, 5, "ser_vtpglut_type: Failed to establish coms\n");
|
|
return instUnknown;
|
|
}
|
|
|
|
a1logd(p->log, 5, "ser_vtpglut_type: Got coms with device\n");
|
|
|
|
if (lumagen) {
|
|
char *bp;
|
|
|
|
/* Get the Lumagen device information */
|
|
if ((se = p->write_read_ex(p, "ZQS01", 0, buf, BUFSZ, NULL, "\n", 1, 2.5, 1)) != 0)
|
|
return instUnknown;
|
|
|
|
/* returns something like "ZQS01!S01,Radiance2020,030115,1016,001309\r\m" */
|
|
if ((bp = strstr(buf, "!")) != NULL && strlen(bp) >= 13) {
|
|
if (strncmp(bp,"!S01,Radiance",13) == 0) {
|
|
rv = devRadiance;
|
|
}
|
|
}
|
|
}
|
|
|
|
a1logd(p->log, 5, "ser_vtpglut_type: Device type is '%s'\n", inst_name(rv));
|
|
|
|
p->close_port(p); /* Or should we leave it open ?? */
|
|
|
|
p->itype = rv;
|
|
|
|
return rv;
|
|
}
|
|
#undef BUFSZ
|
|
|
|
#endif /* ENABLE_SERIAL */
|
|
|
|
#if defined(ENABLE_SERIAL) || defined(ENABLE_FAST_SERIAL)
|
|
|
|
/* Convert an ASCII Hex character to an integer. */
|
|
static int h2b(char c) {
|
|
if (c >= '0' && c <= '9')
|
|
return (c-(int)'0');
|
|
if (c >= 'A' && c <= 'F')
|
|
return (10 + c-(int)'A');
|
|
if (c >= 'a' && c <= 'f')
|
|
return (10 + c-(int)'a');
|
|
return 0;
|
|
}
|
|
|
|
/* Convert a Hex encoded buffer into binary. */
|
|
/* len is number of bytes out */
|
|
static void hex2bin(char *buf, int len) {
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
buf[i] = (char)((h2b(buf[2 * i + 0]) << 4)
|
|
| (h2b(buf[2 * i + 1]) << 0));
|
|
}
|
|
}
|
|
|
|
#endif /* ENABLE_SERIAL */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|