/*
 * Copyright (C) 2015 Marvell International Ltd.
 *
 * This software file (the "File") is distributed by Marvell International
 * Ltd. under the terms of the GNU General Public License Version 2, June 1991
 * (the "License").  You may use, redistribute and/or modify this File in
 * accordance with the terms and conditions of the License, a copy of which
 * is available on the worldwide web at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
 * ARE EXPRESSLY DISCLAIMED.  The License provides additional details about
 * this warranty disclaimer.
 */

/*
 *  Adaptation of Bluez hciattach.c
 *
 *  Copyright (C) 2000-2001  Qualcomm Incorporated
 *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
 *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
 */

#define _GNU_SOURCE

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <syslog.h>
#include <termios.h>
#include <time.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <linux/tty.h>
#include "near.h"

#define FLOW_CTL	0x0001
#define ENABLE_PM	1
#define DISABLE_PM	0

#ifndef N_NCI
#define N_NCI  25
#endif

/* Ioctl */
#define NCIUARTSETDRIVER       _IOW('U', 0, char *)

enum nci_uart_driver {
	NCI_UART_DRIVER_MARVELL = 0,
	NCI_UART_DRIVER_MAX
};

struct nci_driver_info {
	const char *name;
	enum nci_uart_driver driver_id;
};

static struct nci_driver_info __nci_drivers[] = {
	{ .name = "marvell", .driver_id = NCI_UART_DRIVER_MARVELL },
};

static volatile sig_atomic_t __io_canceled = 0;

static void sig_hup(int sig)
{
}

static void sig_term(int sig)
{
	__io_canceled = 1;
}

static int uart_speed(int s)
{
	switch (s) {
	case 9600:
		return B9600;
	case 19200:
		return B19200;
	case 38400:
		return B38400;
	case 57600:
		return B57600;
	case 115200:
		return B115200;
	case 230400:
		return B230400;
	case 460800:
		return B460800;
	case 500000:
		return B500000;
	case 576000:
		return B576000;
	case 921600:
		return B921600;
	case 1000000:
		return B1000000;
	case 1152000:
		return B1152000;
	case 1500000:
		return B1500000;
	case 2000000:
		return B2000000;
#ifdef B2500000
	case 2500000:
		return B2500000;
#endif
#ifdef B3000000
	case 3000000:
		return B3000000;
#endif
#ifdef B3500000
	case 3500000:
		return B3500000;
#endif
#ifdef B3710000
	case 3710000
		return B3710000;
#endif
#ifdef B4000000
	case 4000000:
		return B4000000;
#endif
	default:
		return B57600;
	}
}

static int set_speed(int fd, struct termios *ti, int speed)
{
	if (cfsetospeed(ti, uart_speed(speed)) < 0)
		return -errno;

	if (cfsetispeed(ti, uart_speed(speed)) < 0)
		return -errno;

	if (tcsetattr(fd, TCSANOW, ti) < 0)
		return -errno;

	return 0;
}

/* Initialize UART driver */
static int init_uart(char *dev, const char *driver, int speed, int flow_control)
{
	struct termios ti;
	int fd;
	unsigned int i;
	int driver_id = -1;

	/* Find the driver */
	for (i = 0; i < sizeof (__nci_drivers) / sizeof (__nci_drivers[0]); ++i)
		if (!strcmp(driver, __nci_drivers[i].name)) {
			driver_id = __nci_drivers[i].driver_id;
			break;
		}

	if (driver_id < 0) {
		errno = ENOENT;
		perror("unknown driver");
		return -1;
	}


	fd = open(dev, O_RDWR | O_NOCTTY);
	if (fd < 0) {
		perror("Can't open serial port");
		return -1;
	}

	tcflush(fd, TCIOFLUSH);

	if (tcgetattr(fd, &ti) < 0) {
		perror("Can't get port settings");
		return -1;
	}

	cfmakeraw(&ti);

	ti.c_cflag |= CLOCAL;
	if (flow_control)
		ti.c_cflag |= CRTSCTS;
	else
		ti.c_cflag &= ~CRTSCTS;

	if (tcsetattr(fd, TCSANOW, &ti) < 0) {
		perror("Can't set port settings");
		return -1;
	}

	/* Set initial baudrate */
	if (set_speed(fd, &ti, speed) < 0) {
		perror("Can't set baud rate");
		return -1;
	}

	tcflush(fd, TCIOFLUSH);

	/* Set TTY to N_NCI line discipline */
	i = N_NCI;
	if (ioctl(fd, TIOCSETD, &i) < 0) {
		perror("Can't set line discipline");
		return -1;
	}

	if (ioctl(fd, NCIUARTSETDRIVER, driver_id) < 0) {
		perror("Can't set driver");
		return -1;
	}

	return fd;
}

static void usage(void)
{
	printf("nciattach - NCI UART driver initialization utility\n");
	printf("Usage:\n");
	printf("\tnciattach [-n] [-p] <tty> <driver> "
	       "[speed] [flow|noflow]\n");
}

int main(int argc, char *argv[])
{
	int detach, printpid, opt, i, n, ld, err;
	pid_t pid;
	int speed = 115200;
	int flow_control = 0;
	struct sigaction sa;
	struct pollfd p;
	sigset_t sigs;
	char dev[PATH_MAX];
	char driver[32];

	detach = 1;
	printpid = 0;

	while ((opt=getopt(argc, argv, "np")) != EOF) {
		switch(opt) {
		case 'n':
			detach = 0;
			break;

		case 'p':
			printpid = 1;
			break;

		default:
			usage();
			exit(1);
		}
	}

	n = argc - optind;
	if (n < 2) {
		usage();
		exit(1);
	}

	for (n = 0; optind < argc; n++, optind++) {
		char *option;

		option = argv[optind];

		switch(n) {
		case 0:
			dev[0] = 0;
			if (!strchr(option, '/'))
				strcpy(dev, "/dev/");
			strcat(dev, option);
			break;

		case 1:
			memset(driver, 0, sizeof (driver));
			strcpy(driver, argv[optind]);
			break;

		case 2:
			speed = atoi(argv[optind]);
			break;

		case 3:
			if (!strcmp("flow", argv[optind]))
				flow_control = 1;
			else
				flow_control = 0;
			break;
		}
	}

	memset(&sa, 0, sizeof(sa));

	n = init_uart(dev, driver, speed, flow_control);
	if (n < 0) {
		perror("Can't initialize device");
		exit(1);
	}

	memset(&sa, 0, sizeof(sa));
	sa.sa_flags   = SA_NOCLDSTOP;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGCHLD, &sa, NULL);
	sigaction(SIGPIPE, &sa, NULL);

	sa.sa_handler = sig_term;
	sigaction(SIGTERM, &sa, NULL);
	sigaction(SIGINT,  &sa, NULL);

	sa.sa_handler = sig_hup;
	sigaction(SIGHUP, &sa, NULL);

	if (detach) {
		if ((pid = fork())) {
			if (printpid)
				printf("%d\n", pid);
			return 0;
		}

		for (i = 0; i < 20; i++)
			if (i != n)
				close(i);
	}

	p.fd = n;
	p.events = POLLERR | POLLHUP;

	sigfillset(&sigs);
	sigdelset(&sigs, SIGCHLD);
	sigdelset(&sigs, SIGPIPE);
	sigdelset(&sigs, SIGTERM);
	sigdelset(&sigs, SIGINT);
	sigdelset(&sigs, SIGHUP);

	while (!__io_canceled) {
		p.revents = 0;
		err = ppoll(&p, 1, NULL, &sigs);
		if (err < 0 && errno == EINTR)
			continue;
		if (err)
			break;
	}

	/* Restore TTY line discipline */
	ld = N_TTY;
	if (ioctl(n, TIOCSETD, &ld) < 0) {
		perror("Can't restore line discipline");
		exit(1);
	}

	return 0;
}
