/*
* Samsung HL-R5668W HD Television RS-232 Serial Interface Driver
* Copyright (C) 2006 Eric Gumtow
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* 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.
*  51 Franklin Street, Fifth Floor
*  Boston, MA 02110-1301, USA
*
* ejgumtow@yahoo.com
* http://www.ejgumtow.com/
*/


#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdarg.h>

#include "Samsung_HL-R5668W_driver.h"
#include "Samsung_HL-R5668W_driver_pvt.h"


#define NUM_CMDS(x) (sizeof(x) / sizeof(r5668w_cmd_template_t))

r5668w_cmd_template_t cmds[] = {
	{POWER,                  0x00, 0x00, 0x00, 0, 0,               0},
	{VOLUME,                 0x01, 0x00, 0x00, 1, 0,               100},
	{MUTE,                   0x02, 0x00, 0x00, 0, 0,               0},
	{INPUT_SOURCE,           0x0a, 0x00, 0x00, 1, TV,              HDMI2},
	{PICTURE_MODE,           0x0b, 0x00, 0x00, 1, DYNAMIC,         PICT_CUSTOM},
	{PICTURE_CONTRAST,       0x0b, 0x00, 0x01, 1, 0,               100},
	{PICTURE_BRIGHTNESS,     0x0b, 0x00, 0x02, 1, 0,               100},
	{PICTURE_SHARPNESS,      0x0b, 0x00, 0x03, 1, 0,               100},
	{PICTURE_COLOR,          0x0b, 0x00, 0x04, 1, 0,               100},
	{PICTURE_TINT,           0x0b, 0x00, 0x05, 1, 0,               100},
	{PICTURE_TONE,           0x0b, 0x00, 0x06, 1, COOL2,           WARM2},
	{PICTURE_RESET,          0x0b, 0x00, 0x07, 0, 0,               0},
	{PICTURE_SIZE,           0x0b, 0x01, 0x00, 1, SIXTEEN_BY_NINE, PANORAMA},
	{PICTURE_DIG_NR,         0x0b, 0x02, 0x00, 1, OFF,             ON},
	{PICTURE_DNIE,           0x0b, 0x03, 0x00, 1, DNIE_ON,         DNIE_DEMO},
	{PICTURE_EASY_CONTROL,   0x0b, 0x04, 0x00, 1, RED,             CLR_CUSTOM},
	{PICTURE_DETAIL_RED,     0x0b, 0x04, 0x01, 1, 0,               100},
	{PICTURE_DETAIL_GREEN,   0x0b, 0x04, 0x02, 1, 0,               100},
	{PICTURE_DETAIL_BLUE,    0x0b, 0x04, 0x03, 1, 0,               100},
	{PICTURE_DETAIL_YELLOW,  0x0b, 0x04, 0x04, 1, 0,               100},
	{PICTURE_DETAIL_PINK,    0x0b, 0x04, 0x05, 1, 0,               100},
	{PICTURE_DETAIL_RESET,   0x0b, 0x04, 0x07, 0, 0,               0},
	{PICTURE_FILM_MODE,      0x0b, 0x05, 0x00, 1, OFF,             ON},
	{PICTURE_PIP,            0x0b, 0x06, 0x00, 1, OFF,             ON},
	{PICTURE_PIP_SOURCE,     0x0b, 0x06, 0x01, 1, TV,              COMPONENT2},
	{PICTURE_PIC_SWAP,       0x0b, 0x06, 0x02, 0, 0,               0},
	{PICTURE_PIP_SIZE,       0x0b, 0x06, 0x03, 1, SMALL,           DOUBLE_WIDE},
	{PICTURE_PIP_POSITION,   0x0b, 0x06, 0x04, 1, LOWER_RIGHT,     LOWER_LEFT},
	{PICTURE_PIP_AIR_CATV,   0x0b, 0x06, 0x05, 1, AIR,             CABLE},
	{SOUND_MODE,             0x0c, 0x00, 0x00, 1, SOUND_STANDARD,  CUSTOM},
	{SOUND_EQLZR_LR,         0x0c, 0x01, 0x01, 1, 0,               20},
	{SOUND_EQLZR_100HZ,      0x0c, 0x01, 0x02, 1, 0,               20},
	{SOUND_EQLZR_300HZ,      0x0c, 0x01, 0x03, 1, 0,               20},
	{SOUND_EQLZR_1KHZ,       0x0c, 0x01, 0x04, 1, 0,               20},
	{SOUND_EQLZR_3KHZ,       0x0c, 0x01, 0x05, 1, 0,               20},
	{SOUND_EQLZR_10KHZ,      0x0c, 0x01, 0x06, 1, 0,               20},
	{SOUND_SRS_TSXT,         0x0c, 0x02, 0x00, 1, SRS_OFF,         SRS_STEREO},
	{SOUND_LANGUAGE,         0x0c, 0x03, 0x00, 1, ENGLISH,         FRENCH},
	{SOUND_MULTI_TRACK,      0x0c, 0x03, 0x01, 1, MONO,            SAP},
	{SOUND_AUTO_VOLUME,      0x0c, 0x04, 0x00, 1, OFF,             ON},
	{SOUND_INTERNAL_MUTE,    0x0c, 0x05, 0x00, 1, OFF,             ON},
	{SOUND_DIGITAL_OUTPUT,   0x0c, 0x06, 0x00, 1, OFF,             ON},
	{SOUND_SELECT,           0x0c, 0x07, 0x00, 1, OFF,             ON},
	{SOUND_SUBWOOFER,        0x0c, 0x08, 0x00, 1, OFF,             ON},
	{SOUND_SUBWOOFER_VOLUME, 0x0c, 0x08, 0x01, 1, 0,               100},
	{SOUND_SUBWOOFER_FREQ,   0x0c, 0x08, 0x02, 1, 50,              200},
	{SOUND_MELODY,           0x0c, 0x09, 0x00, 1, OFF,             ON}
};


r5668w_stats_t stats;

/*
 * r5668w_print_dbg_stats()
 * 
 * A useful function to help figure out what part of this program is
 * failing and why it is failing.  You need to compile with the DEBUG
 * symbol defined.
 */
void
r5668w_print_dbg_stats(void)
{
	/*
	 * Hitting a compile error in this function?  You need to change
	 * the definition of the PRINT_STAT() macro in
	 * Samsung_HL-R5668W_driver_pvt.h.
	 */

	PRINT_STAT(port_open_ok);
	PRINT_STAT(port_open_errs);
	PRINT_STAT(port_get_errs);
	PRINT_STAT(port_set_errs);
	PRINT_STAT(port_close);
	PRINT_STAT(wrt_cmd_errs);
	PRINT_STAT(response_get_errs);
	PRINT_STAT(put_cmd_ok);
	PRINT_STAT(response_read_errs);
	PRINT_STAT(response_hdr_errs);
	PRINT_STAT(response_cmd_errs);
	PRINT_STAT(response_bad_status_errs);
	PRINT_STAT(response_ok);
	PRINT_STAT(power_cmds);
	PRINT_STAT(volume_cmds);
	PRINT_STAT(mute_cmds);
	PRINT_STAT(input_cmds);
	PRINT_STAT(cmd_invalid);
	PRINT_STAT(cmd_param_errs);
}	

/*
 * r5668w_open_tv()
 *
 * input: port - the device connected to the television.
 * output: the file descriptor or -1 for failure.
 *
 * Opens the serial port and sets the attributes correctly.
 */
int
r5668w_open_tv(char *port)
{
	int tv;
	struct termios props;

	tv = open(port, O_RDWR);

	if (tv == -1) {
		STAT(port_open_errs);
		goto err;
	}

	if (tcgetattr(tv, &props)) {
		STAT(port_get_errs);
		goto err;
	}

	memset(&props, 0, sizeof(props));
	props.c_cflag = B19200 | CREAD | CS8 | CLOCAL;
	props.c_cc[VTIME] = 2;

	if (tcsetattr(tv, TCSANOW, &props)) {
		STAT(port_set_errs);
		goto err;
	}

	STAT(port_open_ok);
	return tv;

err:

	if (tv != -1) {
		close(tv);
	}
	return -1;
}

/*
 * r5668w_close_tv()
 * 
 * input: tv - the descriptor for the serial port to close.
 * output: see man(2) close.
 *
 * Closes the serial port.
 */
int
r5668w_close_tv(int tv)
{
	STAT(port_close);
	return close(tv);
}

static int
r5668w_put_cmd(int tv, r5668w_cmd_t *cmd)
{
	r5668w_tx_pkt_t tx_pkt;

	tx_pkt.hdr1 = 0x08;
	tx_pkt.hdr2 = 0x22;
	tx_pkt.cmd.b1 = cmd->b1;
	tx_pkt.cmd.b2 = cmd->b2;
	tx_pkt.cmd.b3 = cmd->b3;
	tx_pkt.cmd.val = cmd->val;
	tx_pkt.cksum = ~(0x08+0x22+cmd->b1+cmd->b2+cmd->b3+cmd->val) + 1;

	if (write(tv, &tx_pkt, sizeof(tx_pkt)) != sizeof(tx_pkt)) {
		STAT(wrt_cmd_errs);
		return -1;
	}
	sleep(1);
	if (r5668w_get_response(tv)) {
		STAT(response_get_errs);
		return -1;
	}

	STAT(put_cmd_ok);
	return 0;
}

static int
r5668w_get_response(int tv)
{
	r5668w_rx_pkt_t rx_pkt;

	memset(&rx_pkt, 0, sizeof(rx_pkt));
	if (read(tv, &rx_pkt, sizeof(rx_pkt)) != sizeof(rx_pkt)) {
		STAT(response_read_errs);
		return -1;
	}
	if (rx_pkt.hdr1 != 0x03 || rx_pkt.hdr2 != 0x0c) {
		STAT(response_hdr_errs);
		return -1;
	}
	if (rx_pkt.status == 0xff) {
		STAT(response_cmd_errs);
		return -1;
	}
	if (rx_pkt.status != 0xf1) {
		STAT(response_bad_status_errs);
		return -1;
	}

	STAT(response_ok);
	return 0;
}

/*
 * r5668w_cmd()
 *
 * input: tv - the file descriptor for the serial device.
 *        option - the television setting to modify (volume, channel, etc.).
 *        ... - one optional arg to set the value, like 0-100 for volume.
 *              most options require one value, some require none.
 * output: 0 on success and -1 for failure.
 *
 * Issue a command to the television.
 */
int
r5668w_cmd(int tv, int option, ...)
{
	va_list arg;
	int i, val = 0;
	r5668w_cmd_template_t *template = NULL;
	r5668w_cmd_t cmd;

	if (tv < 0) {
		return -1;
	}

	for (i = 0; i < NUM_CMDS(cmds) && !template; i++) {
		if (cmds[i].option == option) {
			template = &cmds[i];
		}
	}

	if (!template) {
		STAT(cmd_invalid);
		return -1;
	}

	if (template->need_val) {
		va_start(arg, option);
		val = va_arg(arg, int);
		va_end(arg);
	}

	if (val < template->min_val || val > template->max_val) {
		STAT(cmd_param_errs);
		return -1;
	}

	cmd.b1 = template->cmd1;
	cmd.b2 = template->cmd2;
	cmd.b3 = template->cmd3;
	cmd.val = val;

	return r5668w_put_cmd(tv, &cmd);
}
