From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- spectro/i1disp.c | 2591 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2591 insertions(+) create mode 100644 spectro/i1disp.c (limited to 'spectro/i1disp.c') diff --git a/spectro/i1disp.c b/spectro/i1disp.c new file mode 100644 index 0000000..b9da1e1 --- /dev/null +++ b/spectro/i1disp.c @@ -0,0 +1,2591 @@ + +/* + * Argyll Color Correction System + * + * Gretag i1Display related functions + * + * Author: Graeme W. Gill + * Date: 18/10/2006 + * + * Copyright 2006 - 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. + */ + +/* + If you make use of the instrument driver code here, please note + that it is the author(s) of the code who take responsibility + for its operation. Any problems or queries regarding driving + instruments with the Argyll drivers, should be directed to + the Argyll's author(s), and not to any other party. + + If there is some instrument feature or function that you + would like supported here, it is recommended that you + contact Argyll's author(s) first, rather than attempt to + modify the software yourself, if you don't have firm knowledge + of the instrument communicate protocols. There is a chance + that an instrument could be damaged by an incautious command + sequence, and the instrument companies generally cannot and + will not support developers that they have not qualified + and agreed to support. + */ + +#include +#include +#include +#include +#include +#include +#include +#ifndef SALONEINSTLIB +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#else /* !SALONEINSTLIB */ +#include "sa_config.h" +#include "numsup.h" +#endif /* !SALONEINSTLIB */ +#include "xspect.h" +#include "insttypes.h" +#include "conv.h" +#include "icoms.h" +#include "i1disp.h" + +static void dump_bytes(a1log *log, char *pfx, unsigned char *buf, int base, int len); +static inst_code i1disp_interp_code(inst *pp, int ec); +static inst_code i1disp_do_fcal_setit(i1disp *p); +static inst_code i1disp_check_unlock(i1disp *p); + +#define MAX_MES_SIZE 500 /* Maximum normal message reply size */ +#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */ + +#define CALFACTOR 3.428 /* Emissive magic calibration factor */ + +/* ------------------------------------------------------------------------ */ +/* Implementation */ + +/* Interpret an icoms error into a I1DISP error */ +static int icoms2i1disp_err(int se) { + if (se != ICOM_OK) + return I1DISP_COMS_FAIL; + return I1DISP_OK; +} + +/* i1Display command codes - number:X is argument count:return type */ +/* B = byte (8bit), S = short (16bit), W = word (32bit), */ +/* A = string (5 bytes total max) , - = none */ +typedef enum { + i1d_status = 0x00, /* -:A Get status string */ + i1d_rd_red = 0x01, /* -:W Read the red channel clk count (and trig ?) */ + i1d_rd_green = 0x02, /* -:W Read the green channel clk count */ + i1d_rd_blue = 0x03, /* -:W Read the blue channel clk count */ + i1d_getmeas_p = 0x04, /* -:W Read the measure refresh period */ + i1d_setintgt = 0x05, /* W:- Set the integration time */ + i1d_getintgt = 0x06, /* -:W Get the integration time */ + i1d_wrreg = 0x07, /* BB:- Write a register value */ + i1d_rdreg = 0x08, /* B:B Read a register value */ + i1d_getmeas_p2 = 0x09, /* -:W Read the measure refresh period (finer ?) */ + i1d_m_red_p = 0x0a, /* B:W Measure the red period for given edge count */ + i1d_m_green_p = 0x0b, /* B:W Measure the green period for given edge count */ + i1d_m_blue_p = 0x0c, /* B:W Measure the blue period for given edge count */ + i1d_m_rgb_p = 0x0d, /* BBB:W Measure the RGB period for given edge count */ + i1d_unlock = 0x0e, /* BBBB:- Unlock the interface */ + + i1d_m_red_p2 = 0x10, /* S:W Measure the red period (16 bit) */ + i1d_m_green_p2 = 0x11, /* S:W Measure the green period (16 bit) */ + i1d_m_blue_p2 = 0x12, /* S:W Measure the blue period (16 bit) */ + /* S = edge count */ + + i1d_m_red_2 = 0x13, /* B:W Measure the red channel (16 bit) */ + /* B = sync mode, typically 1 */ + + i1d_setmedges2 = 0x14, /* SB:- Set number of edges used for measurment 16 bit */ + /* B = channel */ + i1d_getmedges2 = 0x15, /* B:S Get number of edges used for measurment 16 bit */ + /* B = channel */ + + i1d_m_rgb_edge_2 = 0x16, /* -:W Measure RGB Edge (16 bit) */ + + i1d_set_pll_p = 0x11, /* SS:- Set PLL period */ + i1d_get_pll_p = 0x12, /* -:W Get PLL period */ + + i1d_m_rgb_edge_3 = 0x10, /* BBBB:W Measure RGB Edge & return red. */ + /* BBBB = edge counts ??? */ + i1d_g_green_3 = 0x11, /* -:W Get green data */ + i1d_g_blue_3 = 0x12, /* -:W Get blue data */ + + i1d_wrxreg = 0x13, /* SB:- Write an extra register value */ + i1d_rdxreg = 0x14, /* S:B Read an extra register value */ + + i1d_rdexreg = 0x19 /* BS:B Smile: Read an extended register value */ + /* The address range overlapps i1d_rdreg */ +} i1DispCC; + +/* Do a command/response exchange with the i1disp. */ +/* Return the error code */ +/* The i1 display uses a rather convoluted means of communication (historical?). */ +/* Messages from the host are conveyed one byte per USB control message, */ +/* with the byte conveyed in bRequest, with each such control message reading */ +/* 8 bytes of data from the device. The last 8 bytes read in an overall */ +/* message contains up to 5 response bytes, the last always being nul (?). */ +/* Muti-byte quantities are transmitted in big-endian order. */ +/* (Instructions 1,2,3,8 & 9 seem to be retried up to 5 times. We're */ +/* not doing this, as it may just be historical) */ +static inst_code +i1disp_command_1( + i1disp *p, /* i1display object */ + i1DispCC cc, /* Command code */ + unsigned char *in, int insize, /* Parameter to send */ + unsigned char *out, int bsize, int *rsize, /* Parameter returned */ + double to /* Timeout in seconds */ +) { + int requesttype; /* 8 bit request type (USB bmRequestType) */ + int request; /* 8 bit request code (USB bRequest) */ + int value; /* 16 bit value (USB wValue, sent little endian) */ + int index; /* 16 bit index (USB wIndex, sent little endian) */ + int rwsize; /* 16 bit data size (USB wLength, send little endian) */ + int rcc = 0; /* Return cc code from instruction */ + int i, tsize; + unsigned char buf[8]; /* 8 bytes to read */ + int se, ua = 0, rv = inst_ok; + + tsize = insize + 2; + *rsize = 0; + + a1logd(p->log, 4, "i1disp: Sending cmd %02x args '%s'\n",cc, icoms_tohex(in, insize)); + + /* For each byte to be sent */ + for (i = 0; i < tsize; i++) { + unsigned int smsec; + + /* Control message to read 8 bytes */ + requesttype = IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_ENDPOINT; + if (i == 0) /* Count */ + request = insize + 1; + else if (i == 1) /* Command */ + request = (int)cc; + else /* Data */ + request = (int)in[i-2]; + value = i; /* Incrementing count */ + index = (tsize - i - 1); /* Decrementing count */ + rwsize = 8; + + smsec = msec_time(); + if ((se = p->icom->usb_control(p->icom, requesttype, request, value, index, + buf, rwsize, to)) != 0) { + a1logd(p->log, 1, "i1disp: Message send failed with ICOM err 0x%x\n",se); + p->last_com_err = se; + return i1disp_interp_code((inst *)p, I1DISP_COMS_FAIL); + } + + /* We could check the return data. This seems to be what we sent, */ + /* unless it's the last exchange in the message sequence. */ + /* I don't currently know how or if the device signals an error. */ + + /* If this is the last exchange, copy return value out */ + if (i == (tsize-1)) { + *rsize = buf[1]; + if (*rsize > bsize) + *rsize = bsize; + if (*rsize > 5) + *rsize = 5; + memmove(out, buf + 3, *rsize); + + /* buf[2] is usually the cc, except for i1d_unlock. */ + /* If it is not the cc, this may indicate that the command */ + /* should be retried up to a total of 5 times, before */ + /* assuming it has suceeded. */ + rcc = buf[2] & 0xff; + } + } + rv = i1disp_interp_code((inst *)p, icoms2i1disp_err(ua)); + + if (rv == inst_ok && rcc != cc) + rv = i1disp_interp_code((inst *)p, I1DISP_NOT_READY); + + /* Instrument returns "LOCK" to any instruction if it is locked */ + if (rv == inst_ok && *rsize == 5 && strncmp((char *)out,"LOCK",4) == 0) { + rv = i1disp_interp_code((inst *)p, I1DISP_LOCKED); + } + + a1logd(p->log, 4, "i1disp: response '%s' ICOM err 0x%x\n",icoms_tohex(out, *rsize),ua); + + return rv; +} + +/* Do a command/response exchange with the i1disp, taking care of */ +/* a LOCK error */ +static inst_code +i1disp_command( + i1disp *p, /* i1display object */ + i1DispCC cc, /* Command code */ + unsigned char *in, int insize, /* Parameter to send */ + unsigned char *out, int bsize, int *rsize, /* Parameter returned */ + double to /* Timeout in seconds */ +) { + inst_code rv; + + if ((rv = i1disp_command_1(p, cc, in, insize, out, bsize, rsize, to)) == inst_ok) + return rv; + + /* Unlock and try again */ + if ((rv & inst_imask) == I1DISP_LOCKED) { + if ((rv = i1disp_check_unlock(p)) != inst_ok) + return rv; + rv = i1disp_command_1(p, cc, in, insize, out, bsize, rsize, to); + } + return rv; +} + +/* Take an int, and convert it into a byte buffer */ +static void int2buf(unsigned char *buf, int inv) { + buf[0] = (inv >> 24) & 0xff; + buf[1] = (inv >> 16) & 0xff; + buf[2] = (inv >> 8) & 0xff; + buf[3] = (inv >> 0) & 0xff; +} + +/* Take a short, and convert it into a byte buffer */ +static void short2buf(unsigned char *buf, int inv) { + buf[0] = (inv >> 8) & 0xff; + buf[1] = (inv >> 0) & 0xff; +} + +/* Take a word sized return buffer, and convert it to an int */ +static int buf2int(unsigned char *buf) { + int val; + val = (signed char)buf[0]; + val = ((val << 8) + (0xff & buf[1])); + val = ((val << 8) + (0xff & buf[2])); + val = ((val << 8) + (0xff & buf[3])); + return val; +} + +/* Read a byte from a register */ +static inst_code +i1disp_rdreg_byte( + i1disp *p, /* Object */ + int *outp, /* Where to write value */ + int addr /* Register Address, 0 - 127, 159 */ +) { + unsigned char c, buf[16]; + int rsize; + inst_code ev; + + if (p->dtype == 0) { + if (addr < 0 || addr > 127) + return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS); + } else { + if (addr < 0 || addr > 159) + return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS); + } + c = (unsigned char)addr; + + /* Read a byte */ + if ((ev = i1disp_command(p, i1d_rdreg, &c, 1, + buf, 8, &rsize, 0.5)) != inst_ok) + return ev; + + if (rsize != 3) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + + if (buf[0] != c) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_VAL); + + *outp = (int)buf[1]; + + return inst_ok; +} + +/* Read a short from a register */ +static inst_code +i1disp_rdreg_short( + i1disp *p, /* Object */ + int *outp, /* Where to write value */ + int addr /* Register Address, 0 - 126 */ +) { + inst_code ev; + int v, val; + + if ((ev = i1disp_rdreg_byte(p, &v, addr)) != inst_ok) + return ev; + val = v; + + if ((ev = i1disp_rdreg_byte(p, &v, addr+1)) != inst_ok) + return ev; + val = ((val << 8) + (0xff & v)); + + *outp = val; + + return inst_ok; +} + +/* Read a word from a register */ +static inst_code +i1disp_rdreg_word( + i1disp *p, /* Object */ + int *outp, /* Where to write value */ + int addr /* Register Address, 0 - 124 */ +) { + inst_code ev; + int v, val; + + if ((ev = i1disp_rdreg_byte(p, &v, addr)) != inst_ok) + return ev; + val = v; + + if ((ev = i1disp_rdreg_byte(p, &v, addr+1)) != inst_ok) + return ev; + val = ((val << 8) + (0xff & v)); + + if ((ev = i1disp_rdreg_byte(p, &v, addr+2)) != inst_ok) + return ev; + val = ((val << 8) + (0xff & v)); + + if ((ev = i1disp_rdreg_byte(p, &v, addr+3)) != inst_ok) + return ev; + val = ((val << 8) + (0xff & v)); + + *outp = val; + + return inst_ok; +} + + +/* Read a float from a register */ +/* Will return I1DISP_FLOAT_NOT_SET if the float value was 0xffffffff */ +static inst_code +i1disp_rdreg_float( + i1disp *p, /* Object */ + double *outp, /* Where to write value */ + int addr /* Register Address, 0 - 124 */ +) { + inst_code ev; + int val; + + if ((ev = i1disp_rdreg_word(p, &val, addr)) != inst_ok) + return ev; + + if (ev == 0xffffffff) { + return I1DISP_FLOAT_NOT_SET; + } + + *outp = IEEE754todouble((unsigned int)val); + return inst_ok; +} + + +/* Write a byte to a register */ +static inst_code +i1disp_wrreg_byte( + i1disp *p, /* Object */ + int inv, /* Input value */ + int addr /* Register Address, 0 - 127 */ +) { + int cval; + unsigned char ibuf[16], obuf[16]; + int rsize; + inst_code ev; + + inv &= 0xff; + + /* Read it first, to see if it needs writing */ + if ((ev = i1disp_rdreg_byte(p, &cval, addr) ) != inst_ok) + return ev; + + if (cval == inv) /* No need to write */ + return inst_ok; + + ibuf[0] = (unsigned char)addr; + ibuf[1] = (unsigned char)inv; + + /* Write a byte */ + if ((ev = i1disp_command(p, i1d_wrreg, ibuf, 2, + obuf, 8, &rsize, 0.5)) != inst_ok) + return ev; + + if (rsize != 2) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + + if (obuf[0] != addr) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_VAL); + + /* Check it got written properly */ + if ((ev = i1disp_rdreg_byte(p, &cval, addr) ) != inst_ok) + return ev; + + cval &= 0xff; + if (cval != inv) /* No need to write */ + return i1disp_interp_code((inst *)p, I1DISP_EEPROM_WRITE_FAIL); + + return inst_ok; +} + +/* Write a word to a register */ +static inst_code +i1disp_wrreg_word( + i1disp *p, /* Object */ + int inv, /* Where to write value */ + int addr /* Register Address, 0 - 124 */ +) { + inst_code ev; + int v; + + v = (inv >> 24) & 0xff; + if ((ev = i1disp_wrreg_byte(p, v, addr) ) != inst_ok) + return ev; + + v = (inv >> 16) & 0xff; + if ((ev = i1disp_wrreg_byte(p, v, addr+1) ) != inst_ok) + return ev; + + v = (inv >> 8) & 0xff; + if ((ev = i1disp_wrreg_byte(p, v, addr+2) ) != inst_ok) + return ev; + + v = (inv >> 0) & 0xff; + if ((ev = i1disp_wrreg_byte(p, v, addr+3) ) != inst_ok) + return ev; + + return inst_ok; +} + +/* Write a float to a register */ +static inst_code +i1disp_wrreg_float( + i1disp *p, /* Object */ + double inv, /* Value to write */ + int addr /* Register Address, 0 - 124 */ +) { + inst_code ev; + int val; + + val = (int)doubletoIEEE754(inv); + + if ((ev = i1disp_wrreg_word(p, val, addr)) != inst_ok) + return ev; + return inst_ok; +} + +/* ColorMunki Smile: Read a byte from an extended register range */ +static inst_code +i1disp_rdexreg_bytes( + i1disp *p, /* Object */ + unsigned char *outp, /* Where to write values */ + int addr, /* Register Address, 16 bit */ + int len /* Number of bytes, 8 bits */ +) { + unsigned char ibuf[16]; + unsigned char obuf[16]; + int rsize; + inst_code ev; + + if (p->dtype != 2) /* Only ColorMunki Smile ? */ + return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE); + + if (addr < 0 || addr > 0x0200) + return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS); + + if (len < 0 || (addr + len) > 0x0200) + return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS); + + for (; len > 0; ) { + int rlen = len; + if (rlen > 4) + rlen = 4; + + /* Read up to 4 bytes at a time */ + short2buf(ibuf+0, addr); /* Address to read from */ + ibuf[2] = rlen; /* Number of bytes to read */ + if ((ev = i1disp_command(p, i1d_rdexreg, ibuf, 3, + obuf, 16, &rsize, 0.5)) != inst_ok) + return ev; + + if ((rsize < 4 && (rsize != (2 + rlen))) + || (rsize >= 4 && (rsize != (1 + rlen)))) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + + if (obuf[0] != rlen) /* Number of bytes returned */ + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_VAL); + + memcpy(outp + addr, obuf + 1, rlen); + addr += rlen; + len -= rlen; + } + return inst_ok; +} + +/* Read the integration time */ +static inst_code +i1disp_rd_int_time( + i1disp *p, /* Object */ + int *outp /* Where to write value */ +) { + unsigned char buf[16]; + int rsize; + inst_code ev; + + if ((ev = i1disp_command(p, i1d_getintgt, NULL, 0, + buf, 8, &rsize, 0.5)) != inst_ok) + return ev; + + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + + *outp = buf2int(buf); + + return inst_ok; +} + +/* Set the integration time */ +static inst_code +i1disp_wr_int_time( + i1disp *p, /* Object */ + int inv /* Value to write */ +) { + unsigned char buf[16]; + int rsize; + inst_code ev; + + int2buf(buf, inv); + if ((ev = i1disp_command(p, i1d_setintgt, buf, 4, + buf, 8, &rsize, 0.5)) != inst_ok) + return ev; + + return inst_ok; +} + +/* Read the refresh period */ +static inst_code +i1disp_rd_meas_ref_period( + i1disp *p, /* Object */ + int *outp /* Where to write value */ +) { + unsigned char buf[16]; + int rsize; + inst_code ev; + + if ((ev = i1disp_command(p, i1d_getmeas_p, NULL, 0, + buf, 8, &rsize, 1.5)) != inst_ok) + return ev; + + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + + *outp = buf2int(buf); + + return inst_ok; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Take a raw RGB period measurement from the device for an i1d1. */ +/* The time taken to count the given number of L2F clock edges (+ve & -ve) */ +/* is measured in clk clk_freq counts. */ +static inst_code +i1d1_period_measure( + i1disp *p, /* Object */ + int edgec[3], /* Number of clock edges to count */ + double rgb[3] /* Return the number of clk's */ +) { + int i; + unsigned char ibuf[16]; + unsigned char obuf[16]; + int rsize; + inst_code ev; + + /* Sanity check the number of edges */ + for (i = 0; i < 3; i++) { + if (edgec[i] < 1 || edgec[i] > 255) + return i1disp_interp_code((inst *)p, I1DISP_BAD_INT_THRESH); + ibuf[i] = (char)edgec[i]; + } + + /* Do the measurement, and return the Red value */ + if ((ev = i1disp_command(p, i1d_m_rgb_p, ibuf, 3, + obuf, 8, &rsize, 60.0)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[0] = (double)buf2int(obuf); + + /* Get the green value */ + if ((ev = i1disp_command(p, i1d_rd_green, NULL, 0, + obuf, 8, &rsize, 0.5)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[1] = (double)buf2int(obuf); + + /* Get the blue value */ + if ((ev = i1disp_command(p, i1d_rd_blue, NULL, 0, + obuf, 8, &rsize, 0.5)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[2] = (double)buf2int(obuf); + + return inst_ok; +} + +/* Take a cooked period measurement from the device for the i1d1 */ +/* and return the frequency for each sensor. */ +static inst_code +i1d1_take_measurement( + i1disp *p, /* Object */ + int cal, /* nz if black is not to be subtracted */ + double rgb[3] /* Return the rgb frequency values */ +) { + int i; + int edgec[3]; /* Edge count 1..255 for each channel */ + inst_code ev; + + if (p->inited == 0) + return i1disp_interp_code((inst *)p, I1DISP_NOT_INITED); + + if (p->dtype != 0) + return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE); + + /* Do an initial measurement with minimum edge count of 1 */ + edgec[0] = edgec[1] = edgec[2] = 1; + + if ((ev = i1d1_period_measure(p, edgec, rgb)) != inst_ok) + return ev; + + a1logd(p->log, 3, "Initial RGB = %f %f %f\n",rgb[0],rgb[1],rgb[2]); + + /* Compute adjusted edge count, aiming */ + /* for count values of clk_freq = 1 second (~1e6). */ + for (i = 0; i < 3; i++) { + double ns; + if (p->clk_freq > ((255.0 - 0.5) * rgb[i])) + ns = 255.0; + else { + ns = floor(p->clk_freq/rgb[i]) + 0.5; + if (ns < 1.0) + ns = 1.0; + } + edgec[i] = (int)ns; + } + + /* Only if we compute a different edge count, read again */ + if (edgec[0] > 1 || edgec[1] > 1 || edgec[2] > 1) { + double rgb2[3]; /* 2nd RGB Readings */ + + if ((ev = i1d1_period_measure(p, edgec, rgb2)) != inst_ok) + return ev; + + /* Average readings if we repeated a measurement with the same edge count */ + /* (Minor advantage, but may as well use it) */ + for (i = 0; i < 3; i++) { + if (edgec[i] == 1) + rgb[i] = 0.5 * (rgb[i] + rgb2[i]); + else + rgb[i] = rgb2[i]; + } + } + + a1logd(p->log, 3, "scaled %d %d %d gives RGB = %f %f %f\n", edgec[0],edgec[1],edgec[2], rgb[0],rgb[1],rgb[2]); + + /* Compute adjusted readings as a frequency. */ + /* We need to divide the number of edges/2 by the period in seconds */ + for (i = 0; i < 3; i++) { + rgb[i] = (p->rgbadj[i] * 0.5 * (double)edgec[i] * p->clk_freq)/rgb[i]; + a1logd(p->log, 3, "%d sensor frequency = %f\n",i,rgb[i]); + + /* If we're not calibrating the black */ + if (cal == 0) { + rgb[i] -= p->reg103_F[i]; /* Subtract black level */ + a1logd(p->log, 3, "%d after sub black = %f\n",i,rgb[i]); + + if (rgb[i] < 0.0001) + rgb[i] = 0.0001; + a1logd(p->log, 3, "%d after limit min = %f\n",i,rgb[i]); + } + } + a1logd(p->log, 3, "Adjusted RGB = %f %f %f\n",rgb[0],rgb[1],rgb[2]); + + return inst_ok; +} + +/* . . . . . . . . . . . . . . . . . . . . . . . . */ + +/* Take a fixed period frequency measurement from the device for an i1d2. */ +/* This measures edge count over the set integration period. */ + +/* Take a raw measurement using a given integration time. */ +/* The measureent is the count of (both) edges from the L2V */ +/* over the integration time */ +static inst_code +i1d2_freq_measure( + i1disp *p, /* Object */ + double *inttime, /* Integration time in seconds. (Return clock rounded) */ + double rgb[3] /* Return the RGB edge count values */ +) { + unsigned char ibuf[16]; + unsigned char obuf[16]; + int intclks; + int rsize; + inst_code ev; + + if (*inttime > 20.0) /* Hmm */ + *inttime = 20.0; + + intclks = (int)(*inttime * p->iclk_freq + 0.5); + *inttime = (double)intclks / p->iclk_freq; + if (intclks != p->int_clocks) { + if ((ev = i1disp_wr_int_time(p, intclks)) != inst_ok) + return ev; + if ((ev = i1disp_rd_int_time(p, &intclks) ) != inst_ok) + return ev; + p->int_clocks = intclks; + *inttime = (double)p->int_clocks/p->iclk_freq; + } + + /* Do the measurement, and return the Red value */ + ibuf[0] = 1; /* Sync mode 1 */ + if ((ev = i1disp_command(p, i1d_m_red_2, ibuf, 1, + obuf, 8, &rsize, p->inttime + 1.0)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[0] = (double)buf2int(obuf); + + /* Get the green value */ + if ((ev = i1disp_command(p, i1d_rd_green, NULL, 0, + obuf, 8, &rsize, 0.5)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[1] = (double)buf2int(obuf); + + /* Get the blue value */ + if ((ev = i1disp_command(p, i1d_rd_blue, NULL, 0, + obuf, 8, &rsize, 0.5)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[2] = (double)buf2int(obuf); + + return inst_ok; +} + +/* Take a raw measurement that returns the number of clocks */ +/* between and initial edge and edgec[] subsequent edges of the L2F. */ +/* Both edges are counted. */ +static inst_code +i1d2_period_measure( + i1disp *p, /* Object */ + int edgec[3], /* Measurement edge count for each channel */ + double rgb[3] /* Return the RGB clock count values */ +) { + int i; + unsigned char ibuf[16]; + unsigned char obuf[16]; + int rsize; + inst_code ev; + + /* Set the edge count */ + for (i = 0; i < 3; i++) { + short2buf(ibuf, edgec[i]); /* Edge count */ + ibuf[2] = (unsigned char)i; /* Channel number */ + if ((ev = i1disp_command(p, i1d_setmedges2, ibuf, 3, + obuf, 8, &rsize, 1.0)) != inst_ok) + return ev; + } + + /* Do the measurement, and return the Red value */ + if ((ev = i1disp_command(p, i1d_m_rgb_edge_2, ibuf, 0, + obuf, 8, &rsize, 120.0)) != inst_ok) { + return ev; + } + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[0] = (double)buf2int(obuf); + + /* Get the green value */ + if ((ev = i1disp_command(p, i1d_rd_green, NULL, 0, + obuf, 8, &rsize, 0.5)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[1] = (double)buf2int(obuf); + + /* Get the blue value */ + if ((ev = i1disp_command(p, i1d_rd_blue, NULL, 0, + obuf, 8, &rsize, 0.5)) != inst_ok) + return ev; + if (rsize != 5) + return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE); + rgb[2] = (double)buf2int(obuf); + + return inst_ok; +} + +#define ME 1 /* One edge initially (should this be 2 ?) */ + +#ifndef NEVER + +/* ### Quick and precise for low levels, but subject to long */ +/* ### delays if the light level drops during measurement, and */ +/* ### may be less accurate at low levels due to dark noise */ + +/* Take a cooked measurement from the device for the i1d2 */ +static inst_code +i1d2_take_measurement( + i1disp *p, /* Object */ + int refreshm, /* Measure in refresh mode flag */ + double rgb[3] /* Return the rgb values */ +) { + int i; + double rmeas[3]; /* Raw measurement */ + int edgec[3] = {ME,ME,ME}; /* Measurement edge count for each channel */ + int cdgec[3] = {ME,ME,ME}; /* CRT computed edge count for re-measure */ + int mask = 0x0; /* Period measure mask */ + inst_code ev; + + if (p->inited == 0) + return i1disp_interp_code((inst *)p, I1DISP_NOT_INITED); + + if (p->dtype == 0) + return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE); + + a1logd(p->log, 3, "i1d2_take_measurement called with refreshm = %d\n",refreshm); + + /* Do refresh period measurement */ + if (p->dtype == 1 && refreshm && p->rrset == 0) { + if ((ev = i1disp_do_fcal_setit(p)) != inst_ok) + return ev; + + /* Quantize the sample time */ + if (p->refperiod > 0.0) { + int n; + n = (int)ceil(p->dinttime/p->refperiod); + p->inttime = n * p->refperiod; + a1logd(p->log, 3, "i1disp: integration time quantize to %f secs\n",p->inttime); + } else { + p->inttime = p->dinttime; + a1logd(p->log, 3, "i1disp: integration time set to %f secs\n",p->inttime); + } + } + +// Do a frequency measurement first, to reduces chances of a light change causing delays. +// This makes LCD same as CRT as far as quantization errors in high level readings. +// if (refreshm) { + /* Do an initial fixed integration time frequency measurement. */ + a1logd(p->log, 3, "Doing fixed period frequency measurement over %f secs\n",p->inttime); + + if ((ev = i1d2_freq_measure(p, &p->inttime, rmeas)) != inst_ok) + return ev; + + for (i = 0; i < 3; i++) + rgb[i] = p->rgbadj[i] * 0.5 * rmeas[i]/p->inttime; + + a1logd(p->log, 3, "Got %f %f %f raw, %f %f %f Hz\n", + rmeas[0], rmeas[1], rmeas[2], rgb[0], rgb[1], rgb[2]); + + /* Decide whether any channels need re-measuring, */ + /* and computed cooked values. Threshold is a count of 75 */ + for (i = 0; i < 3; i++) { + if (rmeas[i] <= 75.0) { + mask |= (1 << i); /* Yes */ + if (rmeas[i] >= 10.0) { /* Compute target edges */ + cdgec[i] = (int)(2.0 * rgb[i] * p->inttime + 0.5); + if (cdgec[i] > 2000) + cdgec[i] = 2000; + else if (cdgec[i] < ME) + cdgec[i] = ME; + } + } + } +// } else { +// mask = 0x7; +// } + a1logd(p->log, 3, "Re-measure mask = 0x%x\n",mask); + a1logd(p->log, 3, "cdgec = %d %d %d\n",cdgec[0],cdgec[1],cdgec[2]); + + /* If any need re-measuring */ + if (mask != 0) { + + /* See if we need to compute a target edge count */ + for (i = 0; i < 3; i++) { + if ((mask & (1 << i)) && cdgec[i] == ME) + break; + } + + /* Yes we do */ + if (i < 3) { + + a1logd(p->log, 3, "Doing 1st period pre-measurement mask 0x%x, edgec %d %d %d\n", + mask, edgec[0], edgec[1], edgec[2]); + + /* Do an initial measurement of 1 edge to estimate the */ + /* number of edges needed for the whole integration time. */ + if ((ev = i1d2_period_measure(p, edgec, rmeas)) != inst_ok) + return ev; + + a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n", + rmeas[0], rmeas[1], rmeas[2], + (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas[0], + (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas[1], + (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas[2]); + + /* Compute adjusted edge count for channels we're remeasuring, */ + /* aiming for a values of int_clocks. */ + for (i = 0; i < 3; i++) { + double ns; + if ((mask & (1 << i)) == 0) + continue; + if (p->int_clocks > ((2000.0 - 0.5) * rmeas[i])) + ns = 2000.0; /* Maximum edge count */ + else { + ns = floor(p->inttime * edgec[i] * p->clk_freq/rmeas[i] + 0.5); + if (ns < ME) /* Minimum edge count */ + ns = ME; + } + edgec[i] = (int)ns; + } + } + + /* Use frequency computed edge count if available */ + for (i = 0; i < 3; i++) { + if ((mask & (1 << i)) == 0) + continue; + if (cdgec[i] != ME) + edgec[i] = cdgec[i]; + } + + /* If we compute a different edge count, read again */ + if (edgec[0] > ME || edgec[1] > ME || edgec[2] > ME) { + double rmeas2[3]; /* 2nd RGB Readings */ + + a1logd(p->log, 3, "Doing period re-measurement mask 0x%x, edgec %d %d %d\n", + mask, edgec[0], edgec[1], edgec[2]); + + if ((ev = i1d2_period_measure(p, edgec, rmeas2)) != inst_ok) + return ev; + + a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n", + rmeas[0], rmeas[1], rmeas[2], + (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas2[0], + (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas2[1], + (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas2[2]); + + a1logd(p->log, 3, "Int period %f %f %f secs\n", + rmeas2[0]/p->clk_freq, rmeas2[1]/p->clk_freq, rmeas2[2]/p->clk_freq); + + /* Average readings if we repeated a measurement with the same count */ + /* (Minor advantage, but may as well use it) */ + for (i = 0; i < 3; i++) { + if (edgec[i] == ME) + rmeas[i] = 0.5 * (rmeas[i] + rmeas2[i]); + else + rmeas[i] = rmeas2[i]; + } + } + + /* Compute adjusted readings, ovewritting initial cooked values */ + for (i = 0; i < 3; i++) { + if ((mask & (1 << i)) == 0) + continue; + rgb[i] = (p->rgbadj[i] * 0.5 * (double)edgec[i] * p->clk_freq)/rmeas[i]; + a1logd(p->log, 3, "%d after scale = %f\n",i,rgb[i]); + + rgb[i] -= p->reg103_F[i]; /* Subtract black level */ + a1logd(p->log, 3, "%d after sub black = %f\n",i,rgb[i]); + + if (rgb[i] < 0.0001) + rgb[i] = 0.0001; + a1logd(p->log, 3, "%d after limit min = %f\n",i,rgb[i]); + } + } + + a1logd(p->log, 3, "Cooked RGB Hz = %f %f %f\n",rgb[0],rgb[1],rgb[2]); + + return inst_ok; +} + +#else +/* Less precise (worse quatization errors), but more robust */ +/* against excessive delays if the light level drops during measurement. */ +/* Limits period measurement to an edge count < 35, but that can */ +/* still take a long time in the dark. */ + +/* Take a cooked measurement from the device for the i1d2 */ +static inst_code +i1d2_take_measurement( + i1disp *p, /* Object */ + int refreshm, /* Measure in crt mode flag */ + double rgb[3] /* Return the rgb values */ +) { + int i; + double rmeas[3]; /* Raw measurement */ + int edgec[3] = {ME,ME,ME}; /* Measurement edge count for each channel */ + int cdgec[3] = {ME,ME,ME}; /* CRT computed edge count for re-measure */ + int fmask = 0x0; /* Freq re-measure mask */ + int mask = 0x0; /* Period measure mask */ + inst_code ev; + + if (p->inited == 0) + return i1disp_interp_code((inst *)p, I1DISP_NOT_INITED); + + if (p->dtype == 0) + return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE); + + a1logd(p->log, 3, "i1d2_take_measurement called with refreshm = %d\n",refreshm); + + /* Do refresh period measurement */ + if (p->dtype == 1 && refreshm && p->rrset == 0) { + if ((ev = i1disp_do_fcal_setit(p)) != inst_ok) + return ev; + + /* Quantize the sample time */ + if (p->refperiod > 0.0) { + int n; + n = (int)ceil(p->dinttime/p->refperiod); + p->inttime = n * p->refperiod; + a1logd(p->log, 3, "i1disp: integration time quantize to %f secs\n",p->inttime); + } else { + p->inttime = p->dinttime; + a1logd(p->log, 3, "i1disp: ntegration time set to %f secs\n",p->inttime); + } + } + + /* Do an initial fixed integration time frequency measurement. */ + a1logd(p->log, 3, "Doing fixed period frequency measurement over %f secs\n",p->inttime) + + if ((ev = i1d2_freq_measure(p, &p->inttime, rmeas)) != inst_ok) + return ev; + + for (i = 0; i < 3; i++) + rgb[i] = p->rgbadj[i] * 0.5 * rmeas[i]/p->inttime; + + a1logd(p->log, 3, "Got %f %f %f raw, %f %f %f Hz\n", + rmeas[0], rmeas[1], rmeas[2], rgb[0], rgb[1], rgb[2]); + + /* Decide whether any channels need re-measuring. */ + /* Threshold is a count of 75, and switch to period */ + /* measurement mode on count less than 37. */ + fmask = 0x0; + for (i = 0; i < 3; i++) { + + if (rmeas[i] <= 75.0) { + fmask |= (1 << i); /* Yes, do another freq re-measure */ + + if (rmeas[i] <= 37.5) { + mask |= (1 << i); /* Do a period re-measure */ + fmask = 0; /* Don't bother with freq re-measure */ + } + if (rmeas[i] >= 10.0) { /* Compute target edges */ + cdgec[i] = (int)(2.0 * rgb[i] * p->inttime + 0.5); + if (cdgec[i] > 2000) + cdgec[i] = 2000; + else if (cdgec[i] < ME) + cdgec[i] = ME; + } + } + } + a1logd(p->log, 3, "Freq mask = 0x%x, Period mask 0x%x\n",fmask, mask); + a1logd(p->log, 3, "cdgec = %d %d %d\n",cdgec[0],cdgec[1],cdgec[2]); + + /* If there is a frequency re-measure */ + /* ** This doesn't actually work. The quantization error */ + /* for each read is 0.5, but averaging 2 reads it drops */ + /* to 0.354, not the desired 0.25 that would have been */ + /* acheived with double the integration time. ** */ + if (fmask != 0) { + a1logd(p->log, 3, "Doing frequency re-measurement over %f secs\n",p->inttime); + if ((ev = i1d2_freq_measure(p, &p->inttime, rmeas)) != inst_ok) + return ev; + + for (i = 0; i < 3; i++) { + rgb[i] += p->rgbadj[i] * 0.5 * rmeas[i]/p->inttime; + rgb[i] /= 2.0; + } + + a1logd(p->log, 3, "Got %f %f %f raw, %f %f %f Avg. Hz\n", + rmeas[0], rmeas[1], rmeas[2], rgb[0], rgb[1], rgb[2]); + + /* If there is a period re-measure */ + } else if (mask != 0) { + + /* See if we need to compute a target edge count */ + for (i = 0; i < 3; i++) { + if ((mask & (1 << i)) && cdgec[i] == ME) + break; + } + + /* Yes we do */ + if (i < 3) { + + a1logd(p->log, 3, "Doing 1st period pre-measurement mask 0x%x, edgec %d %d %d\n", + mask, edgec[0], edgec[1], edgec[2]); + + /* Do an initial measurement of 1 edge to estimate the */ + /* number of edges needed for the whole integration time. */ + if ((ev = i1d2_period_measure(p, edgec, rmeas)) != inst_ok) + return ev; + + a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n", + rmeas[0], rmeas[1], rmeas[2], + (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas[0], + (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas[1], + (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas[2]); + + /* Compute adjusted edge count for channels we're remeasuring, */ + /* aiming for a values of int_clocks. */ + for (i = 0; i < 3; i++) { + double ns; + if ((mask & (1 << i)) == 0) + continue; + if (p->int_clocks > ((2000.0 - 0.5) * rmeas[i])) + ns = 2000.0; /* Maximum edge count */ + else { + ns = floor(p->inttime * edgec[i] * p->clk_freq/rmeas[i] + 0.5); + if (ns < ME) /* Minimum edge count */ + ns = ME; + } + edgec[i] = (int)ns; + + /* Sanity check cdgec value, in case light level has changed */ + if ((edgec[i] * 3) < (cdgec[i] * 2)) { + cdgec[i] = edgec[i]; + } + } + } + + /* Use frequency computed edge count if available */ + for (i = 0; i < 3; i++) { + if ((mask & (1 << i)) == 0) + continue; + if (cdgec[i] != ME) + edgec[i] = cdgec[i]; + } + + /* If we compute a different edge count, read again */ + if (edgec[0] > ME || edgec[1] > ME || edgec[2] > ME) { + double rmeas2[3]; /* 2nd RGB Readings */ + + a1logd(p->log, 3, "Doing period re-measurement mask 0x%x, edgec %d %d %d\n", + mask, edgec[0], edgec[1], edgec[2]); + + if ((ev = i1d2_period_measure(p, edgec, rmeas2)) != inst_ok) + return ev; + + a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n", + rmeas[0], rmeas[1], rmeas[2], + (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas2[0], + (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas2[1], + (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas2[2]); + + a1logd(p->log, 3, "Int period %f %f %f secs\n", + rmeas2[0]/p->clk_freq, rmeas2[1]/p->clk_freq, rmeas2[2]/p->clk_freq); + + /* Average readings if we repeated a measurement with the same count */ + /* (Minor advantage, but may as well use it) */ + for (i = 0; i < 3; i++) { + if (edgec[i] == ME) + rmeas[i] = 0.5 * (rmeas[i] + rmeas2[i]); + else + rmeas[i] = rmeas2[i]; + } + } + + /* Compute adjusted readings, ovewritting initial cooked values */ + for (i = 0; i < 3; i++) { + if ((mask & (1 << i)) == 0) + continue; + rgb[i] = (p->rgbadj[i] * 0.5 * (double)edgec[i] * p->clk_freq)/rmeas[i]; + a1logd(p->log, 3, "%d after scale = %f\n",i,rgb[i]); + + rgb[i] -= p->reg103_F[i]; /* Subtract black level */ + a1logd(p->log, 3, "%d after sub black = %f\n",i,rgb[i]); + + if (rgb[i] < 0.0001) + rgb[i] = 0.0001; + a1logd(p->log, 3, "%d after limit min = %f\n",i,rgb[i]); + } + } + + a1logd(p->log, 3, "Cooked RGB Hz = %f %f %f\n",rgb[0],rgb[1],rgb[2]); + + return inst_ok; +} +#endif +#undef ME + +/* . . . . . . . . . . . . . . . . . . . . . . . . */ + +/* Take a XYZ measurement from the device */ +static inst_code +i1disp_take_XYZ_measurement( + i1disp *p, /* Object */ + double XYZ[3] /* Return the XYZ values */ +) { + int i, j; + double rgb[3]; /* RGB Readings */ + inst_code ev; + double *mat; /* Pointer to matrix */ + + if (p->dtype == 0) { /* i1 disp 1 */ + if ((ev = i1d1_take_measurement(p, 0, rgb)) != inst_ok) + return ev; + } else { /* i1 disp 2 or ColorMunki Smile */ + int refreshm = 0; /* Assume non-refresh mode */ + + if (p->refrmode && !IMODETST(p->mode, inst_mode_emis_ambient)) { + refreshm = 1; + } + + if ((ev = i1d2_take_measurement(p, refreshm, rgb)) != inst_ok) + return ev; + } + + /* Multiply by calibration matrix to arrive at XYZ */ + if (IMODETST(p->mode, inst_mode_emis_ambient)) { + mat = p->amb; /* Ambient matrix */ + } else { + if (p->icx) + mat = p->reg54_F; /* CRT/factory matrix/CCFL */ + else + mat = p->reg4_F; /* LCD/user matrix/LED */ + } + for (i = 0; i < 3; i++) { + XYZ[i] = 0.0; + for (j = 0; j < 3; j++) { + XYZ[i] += mat[i * 3 + j] * rgb[j]; + } + XYZ[i] *= CALFACTOR; /* Times magic scale factor */ + +#ifdef NEVER + if (p->chroma4) + XYZ[i] *= 4.0/3.0; /* (Not sure about this factor!) */ +#endif + } + + if (!IMODETST(p->mode, inst_mode_emis_ambient)) { + + /* Apply the colorimeter correction matrix */ + icmMulBy3x3(XYZ, p->ccmat, XYZ); + } + + a1logd(p->log, 3, "returning XYZ = %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]); + return inst_ok; +} + +/* Do a black calibration (i1display 1) */ +static inst_code +i1disp_do_black_cal( + i1disp *p /* Object */ +) { + int i; + double rgb1[3], rgb2[3]; /* RGB Readings */ + inst_code ev; + + if (p->dtype != 0) + return i1disp_interp_code((inst *)p, I1DISP_CANT_BLACK_CALIB); + + /* Do a couple of readings without subtracting the black */ + if ((ev = i1d1_take_measurement(p, 1, rgb1)) != inst_ok) + return ev; + if ((ev = i1d1_take_measurement(p, 1, rgb2)) != inst_ok) + return ev; + + /* Average the results */ + for (i = 0; i < 3; i++) { + rgb1[i] = 0.5 * (rgb1[i] + rgb2[i]); + + /* Since the readings are clamped to 0.0001, */ + /* aim for a black level of 0.0001 */ + rgb1[i] -= 0.0001; + } + + a1logd(p->log, 3, "Black rgb = %f %f %f\n",rgb1[0],rgb1[1],rgb1[2]); + + /* Save it to the EEPROM */ + for (i = 0; i < 3; i++) { + if ((ev = i1disp_wrreg_float(p, rgb1[i], 103 + 4 * i)) != inst_ok) + return ev; + p->reg103_F[i] = rgb1[i]; + } + return inst_ok; +} + +/* Measure the refresh rate */ +static inst_code +i1disp_read_refrate( + inst *pp, + double *ref_rate /* Return value, 0.0 if fails */ +) { + i1disp *p = (i1disp *)pp; + int i; + inst_code ev; + double measp = 0.0; + + a1logd(p->log, 3, "Frequency calibration called\n"); + + if (p->dtype != 1) + return inst_unsupported; + + /* Average a few refresh period readings */ + for (i = 0; i < p->nmeasprds; i++) { + int mp; + + /* Measures period in clocks */ + if ((ev = i1disp_rd_meas_ref_period(p, &mp)) != inst_ok) + return ev; + if (mp == 0) { + measp = 0.0; + break; /* Too dark to measure */ + } + measp += (double)mp; + } + + /* Compute the measurement frequency */ + if (measp != 0.0) { + double rrate = (p->clk_freq * (double)p->nmeasprds)/measp; + a1logd(p->log, 3, "Sample frequency measured = %f\n",rrate); + if (ref_rate != NULL) + *ref_rate = rrate; + return inst_ok; + } else { + a1logd(p->log, 3, "No discernable refresh frequency measured\n"); + if (ref_rate != NULL) + *ref_rate = 0.0; + return inst_misread; + } +} + +/* Do a refresh period cailbration */ +static inst_code +i1disp_do_fcal_setit( + i1disp *p /* Object */ +) { + int i; + inst_code ev; + + a1logd(p->log, 3, "Frequency calibration called\n"); + + if (p->dtype != 1) + return i1disp_interp_code((inst *)p, I1DISP_CANT_MEASP_CALIB); + + if ((ev = i1disp_read_refrate((inst *)p, &p->refrate)) != inst_ok + && ev != inst_misread) + return ev; + + if (p->refrate != 0.0) { + p->refperiod = 1.0/p->refrate; + p->refrvalid = 1; + } else { + p->refrvalid = 0; + } + p->rrset = 1; + + return inst_ok; +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Check the device is responding, and unlock if necessary */ +static inst_code +i1disp_check_unlock( + i1disp *p /* Object */ +) { + unsigned char buf[16]; + int rsize; + inst_code ev; + int i, vv; + double ver; + + struct { + unsigned char code[4]; + int *flag; + } codes[] = { + { { 'G','r','M','b' }, NULL }, /* "GrMb" i1 Display */ + { { 'L','i','t','e' }, &p->lite }, /* "Lite" i1 Display LT */ + { { 'M','u','n','k' }, &p->munki }, /* "Munk" ColorMunki Create */ + { { 'O','b','i','W' }, &p->hpdream }, /* "ObiW" HP DreamColor */ + { { 'O','b','i','w' }, &p->hpdream }, /* "Obiw" HP DreamColor */ + { { 'C','M','X','2' }, &p->calmanx2 }, /* "CMX2" Calman X2 */ + { { 0x24,0xb6,0xb5,0x13 }, NULL }, /* ColorMunki Smile */ + { { 'R','G','B','c' }, NULL }, /* */ + { { 'C','E','C','5' }, NULL }, /* */ + { { 'C','M','C','5' }, NULL }, /* */ + { { 'C','M','G','5' }, NULL }, /* */ + { { 0x00,0x00,0x01,0x00 }, NULL }, /* */ + { { 0x09,0x0b,0x0c,0x0d }, NULL }, /* */ + { { 0x0e,0x0e,0x0e,0x0e }, NULL }, /* */ + { { 0x11,0x02,0xde,0xf0 }, NULL }, /* Barco Chroma 5 ? */ + { { ' ',' ',' ',' ' }, (int *)-1 } + }; + + a1logd(p->log, 3, "i1disp: about to check response and unlock instrument if needed\n"); + + /* Get status */ + if ((ev = i1disp_command_1(p, i1d_status, NULL, 0, + buf, 8, &rsize, 0.5)) != inst_ok && (ev & inst_imask) != I1DISP_LOCKED) + return ev; /* An error other than being locked */ + + /* Try and unlock it if it is locked */ + if ((ev & inst_imask) == I1DISP_LOCKED) { + + /* Reset any flags */ + for (i = 0; ;i++) { + if (codes[i].flag == (int *)-1) + break; + if (codes[i].flag != NULL) + *codes[i].flag = 0; + } + + /* Try each code in turn */ + for (i = 0; ;i++) { + if (codes[i].flag == (int *)-1) { + a1logd(p->log, 3, "Failed to find correct unlock code\n"); + return i1disp_interp_code((inst *)p, I1DISP_UNKNOWN_MODEL); + } + + /* Try unlock code. Ignore I1DISP_NOT_READY status. */ + if (((ev = i1disp_command_1(p, i1d_unlock, codes[i].code, 4, + buf, 8, &rsize, 0.5)) & inst_mask) != inst_ok + && (ev & inst_imask) != I1DISP_LOCKED) + return ev; /* Some other sort of failure */ + + /* Get status */ + if ((ev = i1disp_command_1(p, i1d_status, NULL, 0, + buf, 8, &rsize, 0.5)) != inst_ok + && (ev & inst_imask) != I1DISP_LOCKED) + return ev; /* An error other than being locked */ + + if (ev == inst_ok) { /* Correct code */ + if (codes[i].flag != NULL) + *codes[i].flag = 1; + break; + } + } + } + + if (rsize != 5 || !isdigit(buf[0]) || buf[1] != '.' + || !isdigit(buf[2]) || !isdigit(buf[3])) { + return i1disp_interp_code((inst *)p, I1DISP_BAD_STATUS); + } + + buf[4] = '\000'; + ver = atof((char *)buf); + a1logd(p->log, 3, "Version string = %5.3f\n",ver); + + /* Read register 0x79 for the model identifier */ + if ((ev = i1disp_rdreg_byte(p, &vv, 121) ) != inst_ok) { + return ev; + } + vv &= 0xff; + + a1logd(p->log, 3, "Version character = 0x%02x = '%c'\n",vv,vv); + + /* Sequel Chroma 4 with vv == 0xff ? */ + /* Barco Chroma 5 with ver = 5.01 and vv = '5' */ + if (ver >= 4.0 && ver < 5.1 + && (vv == 0xff || vv == 0x35)) { + p->dtype = 0; /* Sequel Chroma 4 ?? */ + p->chroma4 = 1; /* Treat like an Eye-One Display 1 */ + /* !!! Not fully tested !!! */ + } else if (ver >= 5.1 && ver <= 5.3 && vv == 'L') { + p->dtype = 0; /* Eye-One Display 1 */ + } else if (ver >= 6.0 && ver <= 6.29 && vv == 'L') { + p->dtype = 1; /* Eye-One Display 2 */ + + } else if (ver >= 6.0 && ver <= 6.29 && vv == 'M') { + /* ColorMunki Create ? */ + /* ColorMunki Smile */ + if (p->dtype == 0) /* Not sure if this is set by Create */ + p->dtype = 1; + } else { + /* Reject any version or model we don't know about */ + a1logv(p->log, 1, "Version string = %3.1f\nID character = 0x%02x = '%c'\n",ver,vv,vv); + return i1disp_interp_code((inst *)p, I1DISP_UNKNOWN_VERS_ID); + } + + a1logd(p->log, 2, "i1disp: instrument is responding, unlocked, and right type\n"); + + return inst_ok; +} + +/* Read all the relevant register values */ +static inst_code +i1disp_read_all_regs( + i1disp *p /* Object */ +) { + inst_code ev; + int i; + + a1logd(p->log, 3, "i1disp: about to read all the registers\n"); + + /* Serial number */ + if ((ev = i1disp_rdreg_word(p, &p->reg0_W, 0) ) != inst_ok) + return ev; + a1logd(p->log, 3, "serial number = %d\n",p->reg0_W); + + /* LCD/user calibration values */ + for (i = 0; i < 9; i++) { + if ((ev = i1disp_rdreg_float(p, &p->reg4_F[i], 4 + 4 * i) ) != inst_ok) + return ev; + a1logd(p->log, 3, "LCD/user cal[%d] = %f\n",i,p->reg4_F[i]); + } + /* LCD/user calibration time */ + if ((ev = i1disp_rdreg_word(p, &p->reg50_W, 50) ) != inst_ok) + return ev; + a1logd(p->log, 3, "LCD/user calibration time = 0x%x = %s\n",p->reg50_W, ctime_32(&p->reg50_W)); + + /* LCD/user calibration flag */ + if ((ev = i1disp_rdreg_short(p, &p->reg126_S, 126) ) != inst_ok) + return ev; + a1logd(p->log, 3, "User cal flag = 0x%x\n",p->reg126_S); + + + /* CRT/factory calibration values */ + for (i = 0; i < 9; i++) { + if ((ev = i1disp_rdreg_float(p, &p->reg54_F[i], 54 + 4 * i) ) != inst_ok) + return ev; + a1logd(p->log, 3, "CRT/factory cal[%d] = %f\n",i,p->reg54_F[i]); + } + /* CRT/factory calibration flag */ + if ((ev = i1disp_rdreg_word(p, &p->reg90_W, 90) ) != inst_ok) + return ev; + a1logd(p->log, 3, "CRT/factory flag = 0x%x = %s\n",p->reg90_W, ctime_32(&p->reg90_W)); + + + /* Integration clock period in nsec */ + if ((ev = i1disp_rdreg_short(p, &p->reg40_S, 40) ) != inst_ok) + return ev; + a1logd(p->log, 3, "Reg40 = %d\n",p->reg40_S); + + /* Calibration factor */ + if ((ev = i1disp_rdreg_short(p, &p->reg42_S, 42) ) != inst_ok) + return ev; + a1logd(p->log, 3, "Reg42 = %d\n",p->reg42_S); + + /* Calibration factors */ + for (i = 0; i < 3; i++) { + if ((ev = i1disp_rdreg_short(p, &p->reg44_S[i], 44 + 2 * i) ) != inst_ok) + return ev; + a1logd(p->log, 3, "reg44[%d] = %d\n",i,p->reg44_S[i]); + } + + /* Measurement/master clock period */ + if ((ev = i1disp_rdreg_float(p, &p->clk_prd, 94) ) != inst_ok) + return ev; + a1logd(p->log, 3, "Master clock Frequency = %e\n",1/p->clk_prd); + + /* unknown */ + if ((ev = i1disp_rdreg_word(p, &p->reg98_W, 98) ) != inst_ok) + return ev; + a1logd(p->log, 3, "reg98 = 0x%x = %s\n",p->reg98_W,ctime_32(&p->reg98_W)); + + /* unknown */ + if ((ev = i1disp_rdreg_byte(p, &p->reg102_B, 102) ) != inst_ok) + return ev; + a1logd(p->log, 3, "reg102 = 0x%x\n",p->reg102_B); + + /* Dark current calibration values */ + /* Should we set to a default 0.0 if reg126_S < 0xd ?? */ + for (i = 0; i < 3; i++) { + if ((ev = i1disp_rdreg_float(p, &p->reg103_F[i], 103 + 4 * i)) != inst_ok) { + if ((ev & inst_imask) != I1DISP_FLOAT_NOT_SET) + return ev; + p->reg103_F[i] = 0.0; + } + a1logd(p->log, 3, "darkcal[%d] = %f\n",i,p->reg103_F[i]); + } + + /* Unknown byte */ + if ((ev = i1disp_rdreg_byte(p, &p->reg115_B, 115) ) != inst_ok) + return ev; + a1logd(p->log, 3, "Unknown 115 byte = 0x%x\n",p->reg115_B); + + /* device ID byte */ + if ((ev = i1disp_rdreg_byte(p, &p->reg121_B, 121) ) != inst_ok) + return ev; + a1logd(p->log, 3, "device type byte = 0x%x\n",p->reg121_B); + + /* Unlock string */ + for (i = 0; i < 4; i++) { + int vv; + if ((ev = i1disp_rdreg_byte(p, &vv, 122 + i) ) != inst_ok) + return ev; + p->reg122_B[i] = (char)vv; + } + p->reg122_B[i] = '\000'; + a1logd(p->log, 3, "unlock string = '%s'\n",p->reg122_B); + + p->serno[0] = '\000'; + + /* Read extra registers */ + if (p->dtype == 1) { + +#ifdef NEVER /* Not used, so don't bother */ + for (i = 0; i < 3; i++) { + if ((ev = i1disp_rdreg_float(p, &p->reg128_F[i], 128 + 4 * i) ) != inst_ok) + return ev; + a1logd(p->log, 3, "reg128_F[%d] = %f\n",i,p->reg128_F[i]); + } +#endif /* NEVER */ + + for (i = 0; i < 3; i++) { + if ((ev = i1disp_rdreg_float(p, &p->reg144_F[i], 144 + 4 * i) ) != inst_ok) { + if ((ev & inst_imask) != I1DISP_FLOAT_NOT_SET) + return ev; + p->reg144_F[i] = 1.0; + } + a1logd(p->log, 3, "Ambient scale factor [%d] = %f\n",i,p->reg144_F[i]); + } + + /* Read the integration time */ + if ((ev = i1disp_rd_int_time(p, &p->int_clocks) ) != inst_ok) + return ev; + a1logd(p->log, 3, "Integration time = %d\n",p->int_clocks); + + /* ColorMunki Smile extra information */ + /* (Colormunki Create too ????) */ + } else if (p->dtype == 2) { + int i, v; + + /* Smile doesn't have ambient - reg144 seems to contain LCD cal type, */ + /* ie. "CCFLWLED" */ + + /* Serial Number */ + if ((ev = i1disp_rdexreg_bytes(p, (unsigned char *)p->serno, 0x104, 20)) != inst_ok) + return ev; + p->serno[19] = '\000'; + } + a1logd(p->log, 2, "i1disp: all registers read OK\n"); + + return inst_ok; +} + +/* Compute factors that depend on the register values */ +static inst_code +i1disp_compute_factors( + i1disp *p /* Object */ +) { + int i; + + /* Check that certain value are valid */ + if (p->reg0_W == 0xffffffff) + return i1disp_interp_code((inst *)p, I1DISP_BAD_SERIAL_NUMBER); + + /* LCD calibration date valid ? */ + if (p->reg50_W == 0xffffffff) + return i1disp_interp_code((inst *)p, I1DISP_BAD_LCD_CALIBRATION); + + /* The value stored in reg126_S ("user cal flag") seems hard to interpret. */ + /* For the i1display 1&2, it has a value of 0x0d. */ + /* Value 0x07 seems to be for a "user calibration" */ + /* Values 3 & 6 seem to always "errors" as does a value */ + /* < 7 in most circumstances. But the Heidelberg Viewmaker */ + /* (from Sequel Imaging) and Lacie Blue Eye colorimeter seems to have a value of 2. */ + /* The Barco sensor seems to have a value of 0x20 */ + /* The ColorMunki Smile has a value of 0x21 */ + if (p->reg126_S == 0xffffffff || (p->reg126_S < 7 && p->reg126_S != 2)) + return i1disp_interp_code((inst *)p, I1DISP_BAD_LCD_CALIBRATION); + + /* Not quite sure about this, but we're assuming this */ + /* is set to 2 or 0xd if reg4-36 hold the LCD calibration, */ + /* and some other number if they are not set, or set */ + /* to a custom user calibration. */ + if (p->reg126_S != 0x02 + && p->reg126_S != 0x0d + && p->reg126_S != 0x20 + && p->reg126_S != 0x21) + return i1disp_interp_code((inst *)p, I1DISP_BAD_LCD_CALIBRATION); + + if (p->reg90_W == 0xffffffff) + return i1disp_interp_code((inst *)p, I1DISP_BAD_CRT_CALIBRATION); + + /* Compute ambient matrix */ + for (i = 0; i < 9; i++) + p->amb[i] = p->reg144_F[i % 3] * 0.5 * (p->reg4_F[i] + p->reg54_F[i]); + + /* Integration clock frequency */ + p->iclk_freq = 1.0/(p->reg40_S * 1e-9); + + /* Master/Measurement clock frequency */ + p->clk_freq = 1.0/p->clk_prd; + + /* RGB channel calibration factors */ + for (i = 0; i < 3; i++) { + + /* Individual channel calibration factors, typically 1.0 */ + p->rgbadj[i] = (double)p->reg44_S[i] * 100.0/(double)p->reg42_S; + a1logd(p->log, 3, "reg44+%dcalc2 = %f\n",i,p->rgbadj[i]); + } + + /* Set some defaults */ + p->nmeasprds = 5; /* Number of disp refresh period measurments to average */ + /* in doing frequency calibration */ + p->dinttime = 1.0; /* 1.0 second integration time default */ + p->inttime = p->dinttime; /* Current integration time */ + + return inst_ok; +} + +/* ------------------------------------------------------------------------ */ + +/* Establish communications with a I1DISP */ +/* If it's a serial port, use the baud rate given, and timeout in to secs */ +/* Return DTP_COMS_FAIL on failure to establish communications */ +static inst_code +i1disp_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) { + i1disp *p = (i1disp *) pp; + unsigned char buf[16]; + int rsize; + int se; + inst_code ev = inst_ok; + + a1logd(p->log, 2, "i1disp: About to init coms\n"); + + if (p->icom->port_type(p->icom) != icomt_usb) { + a1logd(p->log, 1, "i1disp_init_coms: coms is not the right type!\n"); + return i1disp_interp_code((inst *)p, I1DISP_UNKNOWN_MODEL); + } + + /* Set config, interface, write end point, read end point */ + /* ("serial" end points aren't used - the i1display uses USB control messages) */ + + /* Set config, interface, write end point, read end point, read quanta */ + if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, icomuf_none, 0, NULL)) != ICOM_OK) { + a1logd(p->log, 1, "i1disp_init_coms: set_usbe_port failed ICOM err 0x%x\n",se); + return i1disp_interp_code((inst *)p, icoms2i1disp_err(se)); + } + + /* Check instrument is responding */ + if ((ev = i1disp_command_1(p, i1d_status, NULL, 0, buf, 8, &rsize, 0.5)) != inst_ok + && (ev & inst_imask) != I1DISP_LOCKED) { + a1logd(p->log, 1, "i1disp_init_coms: failed with rv = 0x%x\n",ev); + return ev; + } + + a1logd(p->log, 2, "i1disp: init coms OK\n"); + + p->gotcoms = 1; + return inst_ok; +} + +static inst_code set_default_disp_type(i1disp *p); + +/* Initialise the I1DISP */ +/* return non-zero on an error, with dtp error code */ +static inst_code +i1disp_init_inst(inst *pp) { + i1disp *p = (i1disp *)pp; + inst_code ev = inst_ok; + + a1logd(p->log, 2, "i1disp_init_inst: called\n"); + + if (p->gotcoms == 0) + return i1disp_interp_code((inst *)p, I1DISP_NO_COMS); /* Must establish coms first */ + + /* Check instrument is responding, and right type */ + if ((ev = i1disp_check_unlock(p)) != inst_ok) + return ev; + + /* Read all the registers and store their contents */ + if ((ev = i1disp_read_all_regs(p)) != inst_ok) + return ev; + +#ifdef NEVER + /* Dump all the register space */ + if (p->dtype < 2) { + unsigned char buf[0x200]; + int i, len; + + len = 128; + if (p->dtype != 0) + len = 160; + + for (i = 0; i < len; i++) { + int v; + if ((ev = i1disp_rdreg_byte(p, &v, i)) != inst_ok) { + return ev; + } + buf[i] = v; + } + dump_bytes(p->log, "dump:", buf, 0, len); + + /* Dump ColorMunki Smile extended range */ + /* Main difference is Ascii serial number + other minor unknown */ + } else { + unsigned char buf[0x200]; + + + if ((ev = i1disp_rdexreg_bytes(p, buf, 0, 0x200)) != inst_ok) { + return ev; + } + dump_bytes(p->log, "dump:", buf, 0, 0x200); + } +#endif /* NEVER */ + + if ((ev = i1disp_compute_factors(p)) != inst_ok) + return ev; + + p->trig = inst_opt_trig_user; + + /* Set a default calibration */ + if ((ev = set_default_disp_type(p)) != inst_ok) { + return ev; + } + + p->inited = 1; + a1logd(p->log, 2, "i1disp_init_inst: inited OK\n"); + + return inst_ok; +} + +/* Read a single sample */ +/* Return the dtp error code */ +static inst_code +i1disp_read_sample( +inst *pp, +char *name, /* Strip name (7 chars) */ +ipatch *val, /* Pointer to instrument patch value */ +instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */ + i1disp *p = (i1disp *)pp; + int user_trig = 0; + int rv = inst_protocol_error; + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + if (p->trig == inst_opt_trig_user) { + + if (p->uicallback == NULL) { + a1logd(p->log, 1, "i1disp: inst_opt_trig_user but no uicallback function set!\n"); + return inst_unsupported; + } + + for (;;) { + if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) { + if (rv == inst_user_abort) + return rv; /* Abort */ + if (rv == inst_user_trig) { + user_trig = 1; + break; /* Trigger */ + } + } + msec_sleep(200); + } + /* Notify of trigger */ + if (p->uicallback) + p->uicallback(p->uic_cntx, inst_triggered); + + /* Progromatic Trigger */ + } else { + /* Check for abort */ + if (p->uicallback != NULL + && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort) + return rv; /* Abort */ + } + + /* Read the XYZ value */ + if ((rv = i1disp_take_XYZ_measurement(p, val->XYZ)) != inst_ok) + return rv; + /* This may not change anything since instrument may clamp */ + if (clamp) + icmClamp3(val->XYZ, val->XYZ); + val->loc[0] = '\000'; + if (IMODETST(p->mode, inst_mode_emis_ambient)) + val->mtype = inst_mrt_ambient; + else + val->mtype = inst_mrt_emission; + val->XYZ_v = 1; /* These are absolute XYZ readings ? */ + val->sp.spec_n = 0; + val->duration = 0.0; + + if (user_trig) + return inst_user_trig; + return rv; +} + +/* Insert a colorimetric correction matrix in the instrument XYZ readings */ +/* This is only valid for colorimetric instruments. */ +/* To remove the matrix, pass NULL for the filter filename */ +inst_code i1disp_col_cor_mat( +inst *pp, +double mtx[3][3] +) { + i1disp *p = (i1disp *)pp; + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + if (mtx == NULL) { + icmSetUnity3x3(p->ccmat); + } else { + if (p->cbid == 0) { + a1loge(p->log, 1, "spyd2: can't set col_cor_mat over non base display type\n"); + inst_wrong_setup; + } + icmCpy3x3(p->ccmat, mtx); + } + + return inst_ok; +} + +/* Return needed and available inst_cal_type's */ +static inst_code i1disp_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) { + i1disp *p = (i1disp *)pp; + inst_cal_type n_cals = inst_calt_none; + inst_cal_type a_cals = inst_calt_none; + + if (p->dtype == 0) { /* Eye-One Display 1 */ + a_cals |= inst_calt_emis_offset; + } + + if (p->dtype == 1 && p->refrmode != 0) { + if (p->rrset == 0) + n_cals |= inst_calt_ref_freq; + a_cals |= inst_calt_ref_freq; + } + + if (pn_cals != NULL) + *pn_cals = n_cals; + + if (pa_cals != NULL) + *pa_cals = a_cals; + + return inst_ok; +} + +/* Request an instrument calibration. */ +/* This is use if the user decides they want to do a calibration, */ +/* in anticipation of a calibration (needs_calibration()) to avoid */ +/* requiring one during measurement, or in response to measuring */ +/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */ +/* and then be prepared to setup the right conditions, or ask the */ +/* user to do so, each time the error inst_cal_setup is returned. */ +inst_code i1disp_calibrate( +inst *pp, +inst_cal_type *calt, /* Calibration type to do/remaining */ +inst_cal_cond *calc, /* Current condition/desired condition */ +char id[CALIDLEN] /* Condition identifier (ie. white reference ID) */ +) { + i1disp *p = (i1disp *)pp; + inst_code ev; + inst_cal_type needed, available; + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + id[0] = '\000'; + + if ((ev = i1disp_get_n_a_cals((inst *)p, &needed, &available)) != inst_ok) + return ev; + + /* Translate inst_calt_all/needed into something specific */ + if (*calt == inst_calt_all + || *calt == inst_calt_needed + || *calt == inst_calt_available) { + if (*calt == inst_calt_all) + *calt = (needed & inst_calt_n_dfrble_mask) | inst_calt_ap_flag; + else if (*calt == inst_calt_needed) + *calt = needed & inst_calt_n_dfrble_mask; + else if (*calt == inst_calt_available) + *calt = available & inst_calt_n_dfrble_mask; + + a1logd(p->log,4,"i1disp_calibrate: doing calt 0x%x\n",calt); + + if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */ + return inst_ok; + } + + /* See if it's a calibration we understand */ + if (*calt & ~available & inst_calt_all_mask) { + return inst_unsupported; + } + + /* Do the appropriate calibration */ + if (p->dtype == 0) { /* Eye-One Display 1 */ + if (*calt & inst_calt_emis_offset) { + + if (*calc != inst_calc_man_ref_dark) { + *calc = inst_calc_man_ref_dark; + return inst_cal_setup; + } + + /* Do black offset calibration */ + if ((ev = i1disp_do_black_cal(p)) != inst_ok) + return ev; + + *calt &= ~inst_calt_emis_offset; + } + + } else { /* Eye-One Display 2 */ + if ((*calt & inst_calt_ref_freq) && p->refrmode != 0) { + + if (*calc != inst_calc_emis_white) { + *calc = inst_calc_emis_white; + return inst_cal_setup; + } + + /* Do CRT frequency calibration and set integration time */ + if ((ev = i1disp_do_fcal_setit(p)) != inst_ok) + return ev; + + /* Quantize the sample time */ + if (p->refperiod > 0.0) { + int n; + n = (int)ceil(p->dinttime/p->refperiod); + p->inttime = n * p->refperiod; + a1logd(p->log, 3, "i1disp: integration time quantize to %f secs\n",p->inttime); + } else { + p->inttime = p->dinttime; + a1logd(p->log, 3, "i1disp: integration time set to %f secs\n",p->inttime); + } + + *calt &= ~inst_calt_ref_freq; + } + } + + return inst_ok; +} + +/* Return the last calibrated refresh rate in Hz. Returns: */ +static inst_code i1disp_get_refr_rate(inst *pp, +double *ref_rate +) { + i1disp *p = (i1disp *)pp; + if (p->refrvalid) { + *ref_rate = p->refrate; + return inst_ok; + } else if (p->rrset) { + *ref_rate = 0.0; + return inst_misread; + } + return inst_needs_cal; +} + +/* Set the calibrated refresh rate in Hz. */ +/* Set refresh rate to 0.0 to mark it as invalid */ +/* Rates outside the range 5.0 to 150.0 Hz will return an error */ +static inst_code i1disp_set_refr_rate(inst *pp, +double ref_rate +) { + i1disp *p = (i1disp *)pp; + + if (ref_rate != 0.0 && (ref_rate < 5.0 || ref_rate > 150.0)) + return inst_bad_parameter; + + p->refrate = ref_rate; + if (ref_rate == 0.0) + p->refrvalid = 0; + else { + p->refperiod = 1.0/p->refrate; + p->refrvalid = 1; + } + p->rrset = 1; + + return inst_ok; +} + +/* Error codes interpretation */ +static char * +i1disp_interp_error(inst *pp, int ec) { +// i1disp *p = (i1disp *)pp; + ec &= inst_imask; + switch (ec) { + case I1DISP_INTERNAL_ERROR: + return "Internal software error"; + case I1DISP_COMS_FAIL: + return "Communications failure"; + case I1DISP_UNKNOWN_MODEL: + return "Not a i1 Display"; + case I1DISP_DATA_PARSE_ERROR: + return "Data from i1 Display didn't parse as expected"; + + case I1DISP_OK: + return "No device error"; + + case I1DISP_FLOAT_NOT_SET: + return "Float value is not set in EEPROM"; + case I1DISP_NOT_READY: + return "Command didn't return command code - not ready ?"; + + case I1DISP_BAD_SERIAL_NUMBER: + return "Serial number isn't set"; + case I1DISP_BAD_LCD_CALIBRATION: + return "LCD calibration values aren't set"; + case I1DISP_BAD_CRT_CALIBRATION: + return "CRT calibration values aren't set"; + case I1DISP_EEPROM_WRITE_FAIL: + return "Write to EEPROM failed to verify"; + + case I1DISP_UNEXPECTED_RET_SIZE: + return "Message from instrument has unexpected size"; + case I1DISP_UNEXPECTED_RET_VAL: + return "Message from instrument has unexpected value"; + + case I1DISP_BAD_STATUS: + return "Instrument status is unrecognised format"; + case I1DISP_UNKNOWN_VERS_ID: + return "Instrument version number or ID byte not recognised"; + + /* Internal errors */ + case I1DISP_BAD_REG_ADDRESS: + return "Out of range register address"; + case I1DISP_BAD_INT_THRESH: + return "Out of range integration threshold"; + case I1DISP_NO_COMS: + return "Communications hasn't been established"; + case I1DISP_NOT_INITED: + return "Insrument hasn't been initialised"; + case I1DISP_CANT_BLACK_CALIB: + return "Device doesn't support black calibration"; + case I1DISP_CANT_MEASP_CALIB: + return "Device doesn't support measurment period calibration"; + case I1DISP_WRONG_DEVICE: + return "Wrong type of device for called function"; + default: + return "Unknown error code"; + } +} + + +/* Convert a machine specific error code into an abstract dtp code */ +static inst_code +i1disp_interp_code(inst *pp, int ec) { +// i1disp *p = (i1disp *)pp; + + ec &= inst_imask; + switch (ec) { + + case I1DISP_OK: + case I1DISP_FLOAT_NOT_SET: /* Internal indication */ + case I1DISP_NOT_READY: /* Internal indication */ + return inst_ok; + + case I1DISP_INTERNAL_ERROR: + case I1DISP_BAD_REG_ADDRESS: + case I1DISP_BAD_INT_THRESH: + case I1DISP_NO_COMS: + case I1DISP_NOT_INITED: + case I1DISP_CANT_BLACK_CALIB: + case I1DISP_CANT_MEASP_CALIB: + case I1DISP_WRONG_DEVICE: + case I1DISP_LOCKED: + return inst_internal_error | ec; + + case I1DISP_COMS_FAIL: + return inst_coms_fail | ec; + + case I1DISP_UNKNOWN_MODEL: + case I1DISP_BAD_STATUS: + case I1DISP_UNKNOWN_VERS_ID: + return inst_unknown_model | ec; + + case I1DISP_DATA_PARSE_ERROR: + case I1DISP_UNEXPECTED_RET_SIZE: + case I1DISP_UNEXPECTED_RET_VAL: + return inst_protocol_error | ec; + + case I1DISP_BAD_SERIAL_NUMBER: + case I1DISP_BAD_LCD_CALIBRATION: + case I1DISP_BAD_CRT_CALIBRATION: + case I1DISP_EEPROM_WRITE_FAIL: + return inst_hardware_fail | ec; + + /* return inst_misread | ec; */ + /* return inst_needs_cal_2 | ec; */ + } + return inst_other_error | ec; +} + +/* Destroy ourselves */ +static void +i1disp_del(inst *pp) { + i1disp *p = (i1disp *)pp; + if (p->icom != NULL) + p->icom->del(p->icom); + inst_del_disptype_list(p->dtlist, p->ndtlist); + free(p); +} + +/* Return the instrument capabilities */ +void i1disp_capabilities(inst *pp, +inst_mode *pcap1, +inst2_capability *pcap2, +inst3_capability *pcap3) { + i1disp *p = (i1disp *)pp; + inst_mode cap1 = 0; + inst2_capability cap2 = 0; + + cap1 |= inst_mode_emis_spot + | inst_mode_colorimeter + ; + + cap2 |= inst2_prog_trig + | inst2_user_trig + | inst2_disptype + | inst2_ccmx + ; + + /* i1D2 has refresh display & ambient capability */ + /* but i1D1 & ColorMunki Smile don't */ + if (p->dtype == 1) { + cap1 |= inst_mode_emis_ambient + | inst_mode_emis_refresh_ovd + | inst_mode_emis_norefresh_ovd + ; + + cap2 |= inst2_refresh_rate + | inst2_emis_refr_meas + ; + } + + if (pcap1 != NULL) + *pcap1 = cap1; + if (pcap2 != NULL) + *pcap2 = cap2; + if (pcap3 != NULL) + *pcap3 = inst3_none; +} + +/* Check device measurement mode */ +inst_code i1disp_check_mode(inst *pp, inst_mode m) { + i1disp *p = (i1disp *)pp; + inst_mode cap; + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + pp->capabilities(pp, &cap, NULL, NULL); + + /* Simple filter for most modes */ + if (m & ~cap) + return inst_unsupported; + + /* Only display emission mode supported */ + if (!IMODETST(m, inst_mode_emis_spot) + && !(p->dtype == 1 && IMODETST(m, inst_mode_emis_ambient))) { + return inst_unsupported; + } + + return inst_ok; +} + +/* Set device measurement mode */ +inst_code i1disp_set_mode(inst *pp, inst_mode m) { + i1disp *p = (i1disp *)pp; + inst_code ev; + + if ((ev = i1disp_check_mode(pp, m)) != inst_ok) + return ev; + + p->mode = m; + + if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) /* Must test this first! */ + p->refrmode = 0; + else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) + p->refrmode = 1; + + return inst_ok; +} + +inst_disptypesel i1disp_disptypesel[3] = { + { + inst_dtflags_default, + 1, + "l", + "LCD display", + 0, + 0 + }, + { + inst_dtflags_none, /* flags */ + 2, /* cbix */ + "c", /* sel */ + "CRT display", /* desc */ + 1, /* refr */ + 1 /* ix */ + }, + { + inst_dtflags_end, + 0, + "", + "", + 0, + 0 + } +}; + + +inst_disptypesel smile_disptypesel[3] = { + { + inst_dtflags_default, /* flags */ + 1, /* cbix */ + "f", /* sel */ + "LCD with CCFL backlight", /* desc */ + 0, /* refr */ + 1 /* ix */ + }, + { + inst_dtflags_none, + 0, + "e", + "LCD with White LED backlight", + 0, + 0 + }, + { + inst_dtflags_end, + 0, + "", + "", + 0, + 0 + } +}; + +static void set_base_disptype_list(i1disp *p) { + /* set the base display type list */ + if (p->itype == instSmile) { + p->_dtlist = smile_disptypesel; + } else { + p->_dtlist = i1disp_disptypesel; + } +} + +/* Get mode and option details */ +static inst_code i1disp_get_disptypesel( +inst *pp, +int *pnsels, /* Return number of display types */ +inst_disptypesel **psels, /* Return the array of display types */ +int allconfig, /* nz to return list for all configs, not just current. */ +int recreate /* nz to re-check for new ccmx & ccss files */ +) { + i1disp *p = (i1disp *)pp; + inst_code rv = inst_ok; + + /* Create/Re-create a current list of abailable display types */ + if (p->dtlist == NULL || recreate) { + if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist, + p->_dtlist, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok) + return rv; + } + + if (pnsels != NULL) + *pnsels = p->ndtlist; + + if (psels != NULL) + *psels = p->dtlist; + + return inst_ok; +} + +/* Given a display type entry, setup for that type */ +static inst_code set_disp_type(i1disp *p, inst_disptypesel *dentry) { + int refrmode; + + p->icx = dentry->ix; + p->cbid = dentry->cbid; + refrmode = dentry->refr; + + if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */ + refrmode = 0; + } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) { + refrmode = 1; + } + + if (p->refrmode != refrmode) { + p->rrset = 0; /* This is a hint we may have swapped displays */ + p->refrvalid = 0; + } + p->refrmode = refrmode; + + if (dentry->flags & inst_dtflags_ccmx) { + icmCpy3x3(p->ccmat, dentry->mat); + } else { + icmSetUnity3x3(p->ccmat); + } + + return inst_ok; +} + +/* Setup the default display type */ +static inst_code set_default_disp_type(i1disp *p) { + inst_code ev; + int i; + + if (p->dtlist == NULL) { + if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist, + p->_dtlist, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok) + return ev; + } + + for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) { + if (p->dtlist[i].flags & inst_dtflags_default) + break; + } + if (p->dtlist[i].flags & inst_dtflags_end) { + a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n"); + return inst_internal_error; + } + if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) { + return ev; + } + + return inst_ok; +} + +/* Set the display type */ +static inst_code i1disp_set_disptype(inst *pp, int ix) { + i1disp *p = (i1disp *)pp; + inst_code ev; + inst_disptypesel *dentry; + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + if (p->dtlist == NULL) { + if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist, + p->_dtlist, 1 /* doccss*/, 1 /* doccmx */)) != inst_ok) + return ev; + } + + if (ix < 0 || ix >= p->ndtlist) + return inst_unsupported; + + dentry = &p->dtlist[ix]; + + if ((ev = set_disp_type(p, dentry)) != inst_ok) { + return ev; + } + + return inst_ok; +} + + +/* + * set or reset an optional mode + * + * Since there is no interaction with the instrument, + * was assume that all of these can be done before initialisation. + */ +static inst_code +i1disp_get_set_opt(inst *pp, inst_opt_type m, ...) +{ + i1disp *p = (i1disp *)pp; + inst_code ev; + + /* Get the display type information */ + if (m == inst_opt_get_dtinfo) { + va_list args; + int *refrmode, *cbid; + + va_start(args, m); + refrmode = va_arg(args, int *); + cbid = va_arg(args, int *); + va_end(args); + + if (refrmode != NULL) + *refrmode = p->refrmode; + if (cbid != NULL) + *cbid = p->cbid; + + return inst_ok; + } + + /* Record the trigger mode */ + if (m == inst_opt_trig_prog + || m == inst_opt_trig_user) { + p->trig = m; + return inst_ok; + } + + return inst_unsupported; +} + +/* Constructor */ +extern i1disp *new_i1disp(icoms *icom, instType itype) { + i1disp *p; + if ((p = (i1disp *)calloc(sizeof(i1disp),1)) == NULL) { + a1loge(icom->log, 1, "new_i1disp: malloc failed!\n"); + return NULL; + } + + p->log = new_a1log_d(icom->log); + + p->init_coms = i1disp_init_coms; + p->init_inst = i1disp_init_inst; + p->capabilities = i1disp_capabilities; + p->check_mode = i1disp_check_mode; + p->set_mode = i1disp_set_mode; + p->get_disptypesel = i1disp_get_disptypesel; + p->set_disptype = i1disp_set_disptype; + p->get_set_opt = i1disp_get_set_opt; + p->read_sample = i1disp_read_sample; + p->read_refrate = i1disp_read_refrate; + p->get_n_a_cals = i1disp_get_n_a_cals; + p->calibrate = i1disp_calibrate; + p->col_cor_mat = i1disp_col_cor_mat; + p->get_refr_rate = i1disp_get_refr_rate; + p->set_refr_rate = i1disp_set_refr_rate; + p->interp_error = i1disp_interp_error; + p->del = i1disp_del; + + p->icom = icom; + p->itype = icom->itype; + + if (p->itype == instI1Disp2) + p->dtype = 1; /* i1Display2 */ + + else if (p->itype == instSmile) { + p->dtype = 2; /* Smile */ + } + + icmSetUnity3x3(p->ccmat); /* Set the colorimeter correction matrix to do nothing */ + set_base_disptype_list(p); + + return p; +} + +/* ---------------------------------------------------------------- */ + +// Print bytes as hex to debug log */ +static void dump_bytes(a1log *log, char *pfx, unsigned char *buf, int base, int len) { + int i, j, ii; + char oline[200] = { '\000' }, *bp = oline; + for (i = j = 0; i < len; i++) { + if ((i % 16) == 0) + bp += sprintf(bp,"%s%04x:",pfx,base+i); + bp += sprintf(bp," %02x",buf[i]); + if ((i+1) >= len || ((i+1) % 16) == 0) { + for (ii = i; ((ii+1) % 16) != 0; ii++) + bp += sprintf(bp," "); + bp += sprintf(bp," "); + for (; j <= i; j++) { + if (!(buf[j] & 0x80) && isprint(buf[j])) + bp += sprintf(bp,"%c",buf[j]); + else + bp += sprintf(bp,"."); + } + bp += sprintf(bp,"\n"); + a1logd(log,0,oline); + bp = oline; + } + } +} + + + + -- cgit v1.2.3