selphy_print/backend_kodak6800.c

1127 lines
28 KiB
C
Raw Normal View History

/*
* Kodak 6800/6850 Photo Printer CUPS backend -- libusb-1.0 version
*
* (c) 2013-2019 Solomon Peachy <pizza@shaftnet.org>
*
* Development of this backend was sponsored by:
*
* LiveLink Technology [ www.livelinktechnology.net ]
*
* The latest version of this program can be found at:
*
* http://git.shaftnet.org/cgit/selphy_print.git
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* [http://www.gnu.org/licenses/gpl-3.0.html]
*
* SPDX-License-Identifier: GPL-3.0+
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define BACKEND kodak6800_backend
#include "backend_common.h"
#include "backend_sinfonia.h"
#define USB_VID_KODAK 0x040A
#define USB_PID_KODAK_6800 0x4021
#define USB_PID_KODAK_6850 0x402B
/* File header */
struct kodak6800_hdr {
uint8_t hdr[7]; /* Always 03 1b 43 48 43 0a 00 */
uint8_t jobid; /* Non-zero */
uint16_t copies; /* BE, in BCD format (1-9999) */
uint16_t columns; /* BE */
uint16_t rows; /* BE */
uint8_t size; /* media size; 0x06 for 6x8, 0x00 for 6x4, 0x07 for 5x7 */
uint8_t laminate; /* 0x01 to laminate, 0x00 for not */
uint8_t method; /* 0x00 or 0x01 (for 4x6 on 6x8 media), 0x21 for 2x6, 0x23 for 3x6 */
} __attribute__((packed));
struct kodak68x0_status_readback {
uint8_t hdr; /* Always 01 */
uint8_t status; /* STATUS_* */
uint8_t status1; /* STATUS1_* */
uint32_t status2; /* STATUS2_* */
uint8_t errcode; /* Error ## */
uint32_t lifetime; /* Lifetime Prints (BE) */
uint32_t maint; /* Maint Prints (BE) */
uint32_t media; /* Media Prints (6850), Unknown (6800) (BE) */
uint32_t cutter; /* Cutter Actuations (BE) */
uint8_t nullB[2];
uint8_t errtype; /* seen 0x00 or 0xd0 */
uint8_t donor; /* Percentage, 0-100 */
uint16_t main_boot; /* Always 003 */
uint16_t main_fw; /* seen 6xx/8xx (6850) and 2xx/3xx/4xx (6800) */
uint16_t dsp_boot; /* Always 001 */
uint16_t dsp_fw; /* Seen 5xx (6850) and 1xx (6800) */
uint8_t b1_jobid;
uint8_t b2_jobid;
uint16_t b1_remain; /* Remaining prints in job */
uint16_t b1_complete; /* Completed prints in job */
uint16_t b1_total; /* Total prints in job */
uint16_t b2_remain; /* Remaining prints in job */
uint16_t b2_complete; /* Completed prints in job */
uint16_t b2_total; /* Total prints in job */
uint8_t curve_status; /* Always seems to be 0x00 */
} __attribute__((packed));
#define MAX_MEDIAS 16
struct kodak68x0_media_readback {
uint8_t hdr; /* Always 0x01 */
uint8_t type; /* Media code, KODAK68x0_MEDIA_xxx */
uint8_t null[5];
uint8_t count; /* Always 0x04 (6800) or 0x06 (6850)? */
struct sinfonia_mediainfo_item sizes[];
} __attribute__((packed));
#define CMDBUF_LEN 17
/* Private data structure */
struct kodak6800_ctx {
struct libusb_device_handle *dev;
uint8_t endp_up;
uint8_t endp_down;
int type;
int supports_sub4x6;
2015-08-23 19:11:59 -04:00
uint8_t jobid;
struct sinfonia_mediainfo_item sizes[MAX_MEDIAS];
uint8_t media_count;
uint8_t media_type;
struct kodak68x0_status_readback sts;
struct marker marker;
};
/* Baseline commands */
static int kodak6800_do_cmd(struct kodak6800_ctx *ctx,
void *cmd, int cmd_len,
void *resp, int resp_len,
int *actual_len)
{
int ret;
/* Write command */
if ((ret = send_data(ctx->dev, ctx->endp_down,
cmd, cmd_len)))
return (ret < 0) ? ret : -99;
/* Read response */
ret = read_data(ctx->dev, ctx->endp_up,
resp, resp_len, actual_len);
if (ret < 0)
return ret;
return 0;
}
static void kodak68x0_dump_mediainfo(struct sinfonia_mediainfo_item *sizes,
uint8_t media_count, uint8_t media_type)
{
int i;
if (media_type == KODAK6_MEDIA_NONE) {
INFO("No Media Loaded\n");
return;
}
kodak6_dumpmediacommon(media_type);
INFO("Legal print sizes:\n");
for (i = 0 ; i < media_count ; i++) {
INFO("\t%d: %dx%d (%02x)\n", i,
sizes[i].columns,
sizes[i].rows,
sizes[i].method);
}
INFO("\n");
}
static void kodak6800_fillhdr(uint8_t *req, uint8_t cmd)
{
req[0] = 0x03; /* CMD type */
req[1] = 0x1b; /* ESC */
req[2] = 0x43; /* C */
req[3] = 0x48; /* H */
req[4] = 0x43; /* C */
req[5] = cmd; /* Commmand code */
}
#define MAX_MEDIA_LEN (sizeof(struct kodak68x0_media_readback) + MAX_MEDIAS * sizeof(struct sinfonia_mediainfo_item))
static int kodak6800_get_mediainfo(struct kodak6800_ctx *ctx)
{
struct kodak68x0_media_readback *media;
uint8_t req[16];
int ret, num, i, j;
memset(req, 0, sizeof(req));
media = malloc(MAX_MEDIA_LEN);
if (!media) {
ERROR("Memory allocation failure!\n");
return CUPS_BACKEND_RETRY_CURRENT;
}
for (j = 0 ; j < 2 ; j ++) {
memset(media, 0, sizeof(*media));
kodak6800_fillhdr(req, 0x1a);
req[6] = j;
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, req, sizeof(req),
media, MAX_MEDIA_LEN,
&num))) {
free(media);
return ret;
}
/* Validate proper response */
if (media->hdr != CMD_CODE_OK ||
media->null[0] != 0x00) {
ERROR("Unexpected response from media query!\n");
free(media);
return CUPS_BACKEND_STOP;
}
ctx->media_type = media->type;
for (i = 0; i < media->count ; i++) {
memcpy(&ctx->sizes[ctx->media_count], &media->sizes[i], sizeof(struct sinfonia_mediainfo_item));
ctx->sizes[ctx->media_count].rows = be16_to_cpu(ctx->sizes[ctx->media_count].rows);
ctx->sizes[ctx->media_count].columns = be16_to_cpu(ctx->sizes[ctx->media_count].columns);
ctx->media_count++;
}
if (i < 6)
break;
}
2019-05-12 08:17:56 -04:00
free(media);
return CUPS_BACKEND_OK;
}
static int kodak68x0_canceljob(struct kodak6800_ctx *ctx,
int id)
{
uint8_t req[16];
int ret, num;
memset(req, 0, sizeof(req));
kodak6800_fillhdr(req, 0x13);
req[6] = id;
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, req, sizeof(req),
&ctx->sts, sizeof(ctx->sts),
&num)))
return ret;
/* Validate proper response */
if (ctx->sts.hdr != CMD_CODE_OK) {
ERROR("Unexpected response from job cancel!\n");
return -99;
}
return 0;
}
static int kodak68x0_reset(struct kodak6800_ctx *ctx)
{
uint8_t req[16];
int ret, num;
memset(req, 0, sizeof(req));
kodak6800_fillhdr(req, 0xc0);
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, req, sizeof(req),
&ctx->sts, sizeof(ctx->sts),
&num)))
return ret;
/* Validate proper response */
if (ctx->sts.hdr != CMD_CODE_OK) {
ERROR("Unexpected response from job cancel!\n");
return -99;
}
return 0;
}
static void kodak68x0_dump_status(struct kodak6800_ctx *ctx, struct kodak68x0_status_readback *status)
{
char *detail;
switch (status->status) {
case STATUS_PRINTING:
detail = "Printing";
break;
case STATUS_IDLE:
detail = "Idle";
break;
default:
detail = "Unknown";
break;
}
INFO("Printer Status : %s\n", detail);
INFO("Printer State : %s # %02x %08x %02x\n",
sinfonia_1x45_status_str(status->status1, status->status2, status->errcode),
status->status1, status->status2, status->errcode);
INFO("Bank 1 ID: %u\n", status->b1_jobid);
INFO("\tPrints: %d/%d complete\n",
be16_to_cpu(status->b1_complete), be16_to_cpu(status->b1_total));
INFO("Bank 2 ID: %u\n", status->b2_jobid);
INFO("\tPrints: %d/%d complete\n",
be16_to_cpu(status->b2_complete), be16_to_cpu(status->b2_total));
switch (status->curve_status) {
case CURVE_TABLE_STATUS_INITIAL:
detail = "Initial/Default";
break;
case CURVE_TABLE_STATUS_USERSET:
detail = "User Stored";
break;
case CURVE_TABLE_STATUS_CURRENT:
detail = "Current";
break;
default:
detail = "Unknown";
break;
}
INFO("Tone Curve Status: %s\n", detail);
INFO("Counters:\n");
INFO("\tLifetime : %u\n", be32_to_cpu(status->lifetime));
INFO("\tThermal Head : %u\n", be32_to_cpu(status->maint));
INFO("\tCutter : %u\n", be32_to_cpu(status->cutter));
if (ctx->type == P_KODAK_6850) {
int max = kodak6_mediamax(ctx->media_type);
INFO("\tMedia : %u\n", be32_to_cpu(status->media));
if (max) {
INFO("\t Remaining : %u\n", max - be32_to_cpu(status->media));
} else {
INFO("\t Remaining : Unknown\n");
}
}
INFO("Main FW version : %d\n", be16_to_cpu(status->main_fw));
INFO("DSP FW version : %d\n", be16_to_cpu(status->dsp_fw));
INFO("Donor : %u%%\n", status->donor);
INFO("\n");
}
static int kodak6800_get_status(struct kodak6800_ctx *ctx,
struct kodak68x0_status_readback *status)
{
uint8_t req[16];
int ret, num;
memset(req, 0, sizeof(req));
memset(status, 0, sizeof(*status));
kodak6800_fillhdr(req, 0x03);
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, req, sizeof(req),
status, sizeof(*status),
&num)))
return ret;
/* Validate proper response */
if (status->hdr != CMD_CODE_OK) {
ERROR("Unexpected response from status query!\n");
return -99;
}
/* Byteswap important stuff */
status->status2 = be32_to_cpu(status->status2);
return 0;
}
static int kodak6800_get_tonecurve(struct kodak6800_ctx *ctx, uint8_t curve, char *fname)
{
uint8_t cmdbuf[16];
uint8_t respbuf[64];
int ret, num = 0;
int i;
uint16_t *data = malloc(TONE_CURVE_SIZE);
if (!data) {
ERROR("Memory Allocation Failure\n");
return -1;
}
INFO("Dump Tone Curve to '%s'\n", fname);
/* Initial Request */
kodak6800_fillhdr(cmdbuf, 0x0c);
cmdbuf[6] = 0x54;
cmdbuf[7] = 0x4f;
cmdbuf[8] = 0x4e;
cmdbuf[9] = 0x45;
cmdbuf[10] = 0x72;
cmdbuf[11] = curve;
cmdbuf[12] = PARAM_TABLE_NONE;
cmdbuf[13] = 0x00;
cmdbuf[14] = 0x00;
cmdbuf[15] = 0x00;
respbuf[0] = 0xff;
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, cmdbuf, sizeof(cmdbuf),
respbuf, sizeof(respbuf),
&num)))
/* Validate proper response */
if (respbuf[0] != CMD_CODE_OK) {
ERROR("Unexpected response from tonecurve query!\n");
ret = -99;
goto done;
}
/* Then we can poll the data */
kodak6800_fillhdr(cmdbuf, 0x0c);
cmdbuf[6] = 0x54;
cmdbuf[7] = 0x4f;
cmdbuf[8] = 0x4e;
cmdbuf[9] = 0x45;
cmdbuf[10] = 0x20;
for (i = 0 ; i < 24 ; i++) {
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, cmdbuf, sizeof(cmdbuf),
respbuf, sizeof(respbuf),
&num)))
goto done;
if (num != 64) {
ERROR("Short read! (%d/%d)\n", num, 51);
ret = 4;
goto done;
}
/* Copy into buffer */
memcpy(((uint8_t*)data)+i*64, respbuf, 64);
}
/* Open file and write it out */
{
int tc_fd = open(fname, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
if (tc_fd < 0) {
ret = 4;
goto done;
}
for (i = 0 ; i < 768; i++) {
/* Byteswap appropriately */
data[i] = cpu_to_be16(le16_to_cpu(data[i]));
ret = write(tc_fd, &data[i], sizeof(uint16_t));
}
close(tc_fd);
}
done:
/* We're done */
free(data);
return ret;
}
static int kodak6800_set_tonecurve(struct kodak6800_ctx *ctx, uint8_t curve, char *fname)
{
uint8_t cmdbuf[64];
uint8_t respbuf[64];
int ret, num = 0;
int remain;
uint16_t *data = malloc(TONE_CURVE_SIZE);
uint8_t *ptr;
if (!data) {
ERROR("Memory Allocation Failure\n");
return -1;
}
INFO("Set Tone Curve from '%s'\n", fname);
/* Read in file */
if ((ret = dyesub_read_file(fname, data, TONE_CURVE_SIZE, NULL))) {
ERROR("Failed to read Tone Curve file\n");
goto done;
}
/* Byteswap data to printer's format */
for (ret = 0; ret < (TONE_CURVE_SIZE)/2 ; ret++) {
data[ret] = cpu_to_le16(be16_to_cpu(data[ret]));
}
/* Initial Request */
kodak6800_fillhdr(cmdbuf, 0x0c);
cmdbuf[6] = 0x54;
cmdbuf[7] = 0x4f;
cmdbuf[8] = 0x4e;
cmdbuf[9] = 0x45;
cmdbuf[10] = 0x77;
cmdbuf[11] = curve;
cmdbuf[12] = PARAM_TABLE_NONE;
cmdbuf[13] = 0x00;
cmdbuf[14] = 0x00;
cmdbuf[15] = 0x00;
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, cmdbuf, sizeof(cmdbuf),
respbuf, sizeof(respbuf),
&num)))
/* Validate proper response */
if (num != 51) {
ERROR("Short read! (%d/%d)\n", num, 51);
ret = 4;
goto done;
}
if (respbuf[0] != CMD_CODE_OK) {
ERROR("Unexpected response from tonecurve set!\n");
ret = -99;
goto done;
}
ptr = (uint8_t*) data;
remain = TONE_CURVE_SIZE;
while (remain > 0) {
int count = remain > 63 ? 63 : remain;
cmdbuf[0] = 0x03;
memcpy(cmdbuf+1, ptr, count);
remain -= count;
ptr += count;
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, cmdbuf, count + 1,
respbuf, sizeof(respbuf),
&num)))
if (num != 51) {
ERROR("Short read! (%d/%d)\n", num, 51);
ret = 4;
goto done;
}
if (respbuf[0] != CMD_CODE_OK) {
ERROR("Unexpected response from tonecurve set!\n");
ret = -99;
goto done;
}
};
done:
/* We're done */
free(data);
return ret;
}
static int kodak6800_query_serno(struct libusb_device_handle *dev, uint8_t endp_up, uint8_t endp_down, char *buf, int buf_len)
{
struct kodak6800_ctx ctx = {
.dev = dev,
.endp_up = endp_up,
.endp_down = endp_down,
};
int ret;
int num;
uint8_t resp[33];
uint8_t req[16];
memset(req, 0, sizeof(req));
memset(resp, 0, sizeof(resp));
kodak6800_fillhdr(req, 0x12);
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(&ctx, req, sizeof(req),
resp, sizeof(resp),
&num)))
return ret;
if (num != 32) {
ERROR("Short read! (%d/%d)\n", num, 32);
return -2;
}
strncpy(buf, (char*)resp+24, buf_len);
buf[buf_len-1] = 0;
return 0;
}
static int kodak6850_send_unk(struct kodak6800_ctx *ctx)
{
uint8_t cmdbuf[16];
uint8_t rdbuf[64];
int ret = 0, num = 0;
memset(cmdbuf, 0, sizeof(cmdbuf));
kodak6800_fillhdr(cmdbuf, 0x4c);
/* Issue command and get response */
if ((ret = kodak6800_do_cmd(ctx, cmdbuf, sizeof(cmdbuf),
rdbuf, sizeof(rdbuf),
&num)))
return -1;
if (num != 51) {
ERROR("Short read! (%d/%d)\n", num, 51);
return CUPS_BACKEND_FAILED;
}
if (rdbuf[0] != CMD_CODE_OK ||
rdbuf[2] != 0x43) {
ERROR("Unexpected response from printer init!\n");
return CUPS_BACKEND_FAILED;
}
2017-07-10 20:15:56 -04:00
#if 0
// XXX No particular idea what this actually is
if (rdbuf[1] != 0x01 && rdbuf[1] != 0x00) {
ERROR("Unexpected status code (0x%02x)!\n", rdbuf[1]);
return CUPS_BACKEND_FAILED;
}
#endif
return ret;
}
static void kodak6800_cmdline(void)
{
DEBUG("\t\t[ -c filename ] # Get user/NV tone curve\n");
DEBUG("\t\t[ -C filename ] # Set user/NV tone curve\n");
DEBUG("\t\t[ -l filename ] # Get current tone curve\n");
DEBUG("\t\t[ -L filename ] # Set current tone curve\n");
DEBUG("\t\t[ -m ] # Query media\n");
DEBUG("\t\t[ -s ] # Query status\n");
DEBUG("\t\t[ -R ] # Reset printer\n");
2017-07-10 20:15:56 -04:00
DEBUG("\t\t[ -X jobid ] # Cancel Job\n");
}
static int kodak6800_cmdline_arg(void *vctx, int argc, char **argv)
{
struct kodak6800_ctx *ctx = vctx;
int i, j = 0;
if (!ctx)
return -1;
while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL "C:c:L:l:mRsX:")) >= 0) {
switch(i) {
GETOPT_PROCESS_GLOBAL
case 'c':
j = kodak6800_get_tonecurve(ctx, TONE_TABLE_USER, optarg);
break;
case 'C':
j = kodak6800_set_tonecurve(ctx, TONE_TABLE_USER, optarg);
break;
case 'l':
j = kodak6800_get_tonecurve(ctx, TONE_TABLE_CURRENT, optarg);
break;
case 'L':
j = kodak6800_set_tonecurve(ctx, TONE_TABLE_CURRENT, optarg);
break;
case 'm':
kodak68x0_dump_mediainfo(ctx->sizes, ctx->media_count, ctx->media_type);