/*
 * USB keyboard driver for barebox
 *
 * (C) Copyright 2001 Denis Peter, MPL AG Switzerland
 * (C) Copyright 2015 Peter Mamonov
 *
 * 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.
 *
 */

#include <common.h>
#include <init.h>
#include <clock.h>
#include <poller.h>
#include <usb/usb.h>
#include <string.h>
#include <dma.h>
#include <input/input.h>

/*
 * NOTE: It's important for the NUM, CAPS, SCROLL-lock bits to be in this
 *       order. See usb_kbd_setled() function!
 */
#define USB_KBD_NUMLOCK		(1 << 0)
#define USB_KBD_CAPSLOCK	(1 << 1)
#define USB_KBD_SCROLLLOCK	(1 << 2)
#define USB_KBD_CTRL		(1 << 3)

#define USB_KBD_LEDMASK		\
	(USB_KBD_NUMLOCK | USB_KBD_CAPSLOCK | USB_KBD_SCROLLLOCK)

/*
 * USB Keyboard reports are 8 bytes in boot protocol.
 * Appendix B of HID Device Class Definition 1.11
 */
#define USB_KBD_BOOT_REPORT_SIZE 8

struct usb_kbd_pdata;

struct usb_kbd_pdata {
	uint8_t		*new;
	uint8_t		old[USB_KBD_BOOT_REPORT_SIZE];
	struct poller_async	poller;
	struct usb_device	*usbdev;
	unsigned long	intpipe;
	int		intpktsize;
	int		intinterval;
	struct usb_endpoint_descriptor *ep;
	int (*do_poll)(struct usb_kbd_pdata *);
	struct input_device input;
};

static void usb_kbd_release_all_keys(struct usb_kbd_pdata *data)
{
	int i;

	for (i = 0; i <= KEY_MAX; i++)
		input_report_key_event(&data->input, i, 0);
}

static int usb_kbd_int_poll(struct usb_kbd_pdata *data)
{
	return usb_submit_int_msg(data->usbdev, data->intpipe, data->new,
				  data->intpktsize, data->intinterval);
}

static int usb_kbd_cnt_poll(struct usb_kbd_pdata *data)
{
	struct usb_interface *iface = &data->usbdev->config.interface[0];

	return usb_get_report(data->usbdev, iface->desc.bInterfaceNumber,
			      1, 0, data->new, USB_KBD_BOOT_REPORT_SIZE);
}

static const unsigned char usb_kbd_keycode[256] = {
	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
	115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
	122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
	150,158,159,128,136,177,178,176,142,152,173,140
};

static void usb_kbd_poll(void *arg)
{
	struct usb_kbd_pdata *data = arg;
	struct usb_device *usbdev = data->usbdev;
	int ret, i;

	ret = data->do_poll(data);
	if (ret < 0)
		usb_kbd_release_all_keys(data);
	if (ret == -EAGAIN)
		goto exit;
	if (ret < 0) {
		/* exit with noreturn */
		dev_err(&usbdev->dev,
			"usb_submit_int_msg() failed. Keyboard disconnect?\n");
		return;
	}

	if (!memcmp(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE))
		goto exit;

	pr_debug("%s: new report: %016llx\n", __func__, *((volatile uint64_t *)data->new));

	for (i = 0; i < 8; i++)
		input_report_key_event(&data->input, usb_kbd_keycode[i + 224], (data->new[0] >> i) & 1);

	for (i = 2; i < 8; i++) {
		if (data->old[i] > 3 && memscan(data->new + 2, data->old[i], 6) == data->new + 8) {
			if (usb_kbd_keycode[data->old[i]])
				input_report_key_event(&data->input, usb_kbd_keycode[data->old[i]], 0);
			else
				dev_err(&usbdev->dev,
					 "Unknown key (scancode %#x) released.\n",
					 data->old[i]);
		}

		if (data->new[i] > 3 && memscan(data->old + 2, data->new[i], 6) == data->old + 8) {
			if (usb_kbd_keycode[data->new[i]])
				input_report_key_event(&data->input, usb_kbd_keycode[data->new[i]], 1);
			else
				dev_err(&usbdev->dev,
					 "Unknown key (scancode %#x) pressed.\n",
					 data->new[i]);
		}
	}

	memcpy(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE);

exit:
	poller_call_async(&data->poller, data->intinterval * MSECOND, usb_kbd_poll, data);
}

static int usb_kbd_probe(struct usb_device *usbdev,
			 const struct usb_device_id *id)
{
	int ret;
	struct usb_interface *iface = &usbdev->config.interface[0];
	struct usb_kbd_pdata *data;

	dev_info(&usbdev->dev, "USB keyboard found\n");
	dev_dbg(&usbdev->dev, "Debug enabled\n");
	ret = usb_set_protocol(usbdev, iface->desc.bInterfaceNumber, 0);
	if (ret < 0)
		return ret;

	ret = usb_set_idle(usbdev, iface->desc.bInterfaceNumber, 1, 0);
	if (ret < 0)
		return ret;

	data = xzalloc(sizeof(struct usb_kbd_pdata));
	usbdev->drv_data = data;
	data->new = dma_alloc(USB_KBD_BOOT_REPORT_SIZE);

	data->usbdev = usbdev;

	data->ep = &iface->ep_desc[0];
	data->intpipe = usb_rcvintpipe(usbdev, data->ep->bEndpointAddress);
	data->intpktsize = min(usb_maxpacket(usbdev, data->intpipe),
			       USB_KBD_BOOT_REPORT_SIZE);
	data->intinterval = data->ep->bInterval;
	/* test polling via interrupt endpoint */
	data->do_poll = usb_kbd_int_poll;
	ret = data->do_poll(data);
	if (ret < 0) {
		/* fall back to polling via control enpoint */
		data->do_poll = usb_kbd_cnt_poll;
		usb_set_idle(usbdev,
			     iface->desc.bInterfaceNumber, 0, 0);
		ret = data->do_poll(data);
		if (ret < 0) {
			/* no luck */
			dma_free(data->new);
			free(data);
			return ret;
		} else
			dev_dbg(&usbdev->dev, "poll keyboard via cont ep\n");
	} else
		dev_dbg(&usbdev->dev, "poll keyboard via int ep\n");

	ret = input_device_register(&data->input);
	if (ret) {
		dev_err(&usbdev->dev, "can't register input\n");
		return ret;
	}

	ret = poller_async_register(&data->poller, "usb-kbd");
	if (ret) {
		dev_err(&usbdev->dev, "can't setup poller\n");
		return ret;
	}

	poller_call_async(&data->poller, data->intinterval * MSECOND, usb_kbd_poll, data);

	return 0;
}

static void usb_kbd_disconnect(struct usb_device *usbdev)
{
	struct usb_kbd_pdata *data = usbdev->drv_data;

	usb_kbd_release_all_keys(data);
	poller_async_unregister(&data->poller);
	input_device_unregister(&data->input);
	dma_free(data->new);
	free(data);
}

static struct usb_device_id usb_kbd_usb_ids[] = {
	{ USB_INTERFACE_INFO(3, 1, 1) }, // usb keyboard
	{ }
};

static struct usb_driver usb_kbd_driver = {
	.name =		"usb-keyboard",
	.id_table =	usb_kbd_usb_ids,
	.probe =	usb_kbd_probe,
	.disconnect =	usb_kbd_disconnect,
};

static int __init usb_kbd_init(void)
{
	return usb_driver_register(&usb_kbd_driver);
}
device_initcall(usb_kbd_init);
