/* Copyright 2016 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "clock.h"
#include "common.h"
#include "config.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "hwtimer.h"
#include "link_defs.h"
#include "queue.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "usb_api.h"
#include "usb_descriptor.h"
#include "usb_hw.h"
#include "usb_hid.h"
#include "usb_hid_hw.h"
#include "usb_hid_touchpad.h"

/* Console output macro */
#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)

static const int touchpad_debug;

static struct queue const report_queue = QUEUE_NULL(8,
						struct usb_hid_touchpad_report);
static struct mutex report_queue_mutex;

#define HID_TOUCHPAD_REPORT_SIZE  sizeof(struct usb_hid_touchpad_report)

/*
 * Touchpad EP interval: Make sure this value is smaller than the typical
 * interrupt interval from the trackpad.
 */
#define HID_TOUCHPAD_EP_INTERVAL_MS 2 /* ms */

/* Discard TP events older than this time */
#define EVENT_DISCARD_MAX_TIME (1 * SECOND)

/* HID descriptors */
const struct usb_interface_descriptor USB_IFACE_DESC(USB_IFACE_HID_TOUCHPAD) = {
	.bLength = USB_DT_INTERFACE_SIZE,
	.bDescriptorType = USB_DT_INTERFACE,
	.bInterfaceNumber = USB_IFACE_HID_TOUCHPAD,
	.bAlternateSetting = 0,
	.bNumEndpoints = 1,
	.bInterfaceClass = USB_CLASS_HID,
	.bInterfaceSubClass = 0,
	.bInterfaceProtocol = 0,
	.iInterface = 0,
};
const struct usb_endpoint_descriptor USB_EP_DESC(USB_IFACE_HID_TOUCHPAD, 81) = {
	.bLength = USB_DT_ENDPOINT_SIZE,
	.bDescriptorType = USB_DT_ENDPOINT,
	.bEndpointAddress = 0x80 | USB_EP_HID_TOUCHPAD,
	.bmAttributes = 0x03 /* Interrupt endpoint */,
	.wMaxPacketSize = HID_TOUCHPAD_REPORT_SIZE,
	.bInterval = HID_TOUCHPAD_EP_INTERVAL_MS /* polling interval */
};

#define FINGER_USAGE \
	0x05, 0x0D,        /*   Usage Page (Digitizer) */ \
	0x09, 0x22,        /*   Usage (Finger) */ \
	0xA1, 0x02,        /*   Collection (Logical) */ \
	0x09, 0x47,        /*     Usage (Confidence) */ \
	0x09, 0x42,        /*     Usage (Tip Switch) */ \
	0x09, 0x32,        /*     Usage (In Range) */ \
	0x15, 0x00,        /*     Logical Minimum (0) */ \
	0x25, 0x01,        /*     Logical Maximum (1) */ \
	0x75, 0x01,        /*     Report Size (1) */ \
	0x95, 0x03,        /*     Report Count (3) */ \
	0x81, 0x02,        /*     Input (Data,Var,Abs) */ \
	0x09, 0x51,        /*     Usage (0x51) Contact identifier */ \
	0x75, 0x04,        /*     Report Size (4) */ \
	0x95, 0x01,        /*     Report Count (1) */ \
	0x25, 0x0F,        /*     Logical Maximum (15) */ \
	0x81, 0x02,        /*     Input (Data,Var,Abs) */ \
	0x05, 0x0D,        /*     Usage Page (Digitizer) */ \
			   /*     Logical Maximum of Pressure */ \
	0x26, (CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_PRESSURE & 0xFF), \
	      (CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_PRESSURE >> 8), \
	0x75, 0x09,        /*     Report Size (9) */ \
	0x09, 0x30,        /*     Usage (Tip pressure) */ \
	0x81, 0x02,        /*     Input (Data,Var,Abs) */ \
	0x26, 0xFF, 0x0F,  /*     Logical Maximum (4095) */ \
	0x75, 0x0C,        /*     Report Size (12) */ \
	0x09, 0x48,        /*     Usage (WIDTH) */ \
	0x81, 0x02,        /*     Input (Data,Var,Abs) */ \
	0x09, 0x49,        /*     Usage (HEIGHT) */ \
	0x81, 0x02,        /*     Input (Data,Var,Abs) */ \
	0x05, 0x01,        /*     Usage Page (Generic Desktop Ctrls) */ \
	0x75, 0x0C,        /*     Report Size (12) */ \
	0x55, 0x0E,        /*     Unit Exponent (-2) */ \
	0x65, 0x11,        /*     Unit (System: SI Linear, Length: cm) */ \
	0x09, 0x30,        /*     Usage (X) */ \
	0x35, 0x00,        /*     Physical Minimum (0) */ \
	0x26, (CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_X & 0xff), \
	      (CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_X >> 8), \
	                   /*     Logical Maximum */ \
	0x46, (CONFIG_USB_HID_TOUCHPAD_PHYSICAL_MAX_X & 0xff), \
	      (CONFIG_USB_HID_TOUCHPAD_PHYSICAL_MAX_X >> 8), \
	                   /*     Physical Maximum (tenth of mm) */ \
	0x81, 0x02,        /*     Input (Data,Var,Abs) */ \
	0x26, (CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_Y & 0xff), \
	      (CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_Y >> 8), \
	                   /*     Logical Maximum */ \
	0x46, (CONFIG_USB_HID_TOUCHPAD_PHYSICAL_MAX_Y & 0xff), \
	      (CONFIG_USB_HID_TOUCHPAD_PHYSICAL_MAX_Y >> 8), \
	                   /*     Physical Maximum (tenth of mm) */ \
	0x09, 0x31,        /*     Usage (Y) */ \
	0x81, 0x02,        /*     Input (Data,Var,Abs) */ \
	0xC0               /*   End Collection */

/*
 * HID: Report Descriptor
 * TODO(b/35582031): There are ways to reduce flash usage, as the
 * Finger Usage is repeated 5 times.
 */
static const uint8_t report_desc[] = {
	/* Touchpad Collection */
	0x05, 0x0D,                    /* Usage Page (Digitizer) */
	0x09, 0x05,                    /* Usage (Touch Pad) */
	0xA1, 0x01,                    /* Collection (Application) */
	0x85, REPORT_ID_TOUCHPAD,      /*   Report ID (1, Touch) */
	/* Finger 0 */
	FINGER_USAGE,
	/* Finger 1 */
	FINGER_USAGE,
	/* Finger 2 */
	FINGER_USAGE,
	/* Finger 3 */
	FINGER_USAGE,
	/* Finger 4 */
	FINGER_USAGE,
	/* Contact count */
	0x05, 0x0D,                    /*   Usage Page (Digitizer) */
	0x09, 0x54,                    /*   Usage (Contact count) */
	0x25, MAX_FINGERS,             /*   Logical Maximum (MAX_FINGERS) */
	0x75, 0x07,                    /*   Report Size (7) */
	0x95, 0x01,                    /*   Report Count (1) */
	0x81, 0x02,                    /*   Input (Data,Var,Abs) */
	/* Button */
	0x05, 0x01,                    /*   Usage Page(Generic Desktop Ctrls) */
	0x05, 0x09,                    /*   Usage (Button) */
	0x19, 0x01,                    /*   Usage Minimum (0x01) */
	0x29, 0x01,                    /*   Usage Maximum (0x01) */
	0x15, 0x00,                    /*   Logical Minimum (0) */
	0x25, 0x01,                    /*   Logical Maximum (1) */
	0x75, 0x01,                    /*   Report Size (1) */
	0x95, 0x01,                    /*   Report Count (1) */
	0x81, 0x02,                    /*   Input (Data,Var,Abs) */
	/* Timestamp */
	0x05, 0x0D,                    /*   Usage Page (Digitizer) */
	0x55, 0x0C,                    /*   Unit Exponent (-4) */
	0x66, 0x01, 0x10,              /*   Unit (Seconds) */
	0x47, 0xFF, 0xFF, 0x00, 0x00,  /*   Physical Maximum (65535) */
	0x27, 0xFF, 0xFF, 0x00, 0x00,  /*   Logical Maximum (65535) */
	0x75, 0x10,                    /*   Report Size (16) */
	0x95, 0x01,                    /*   Report Count (1) */
	0x09, 0x56,                    /*   Usage (0x56, Relative Scan Time) */
	0x81, 0x02,                    /*   Input (Data,Var,Abs) */

	0x85, REPORT_ID_DEVICE_CAPS,   /*   Report ID (Device Capabilities) */
	0x09, 0x55,                    /*   Usage (Contact Count Maximum) */
	0x09, 0x59,                    /*   Usage (Pad Type) */
	0x25, 0x0F,                    /*   Logical Maximum (15) */
	0x75, 0x08,                    /*   Report Size (8) */
	0x95, 0x02,                    /*   Report Count (2) */
	0xB1, 0x02,                    /*   Feature (Data,Var,Abs) */

	/* Page 0xFF, usage 0xC5 is device certificate. */
	0x06, 0x00, 0xFF,              /*   Usage Page (Vendor Defined) */
	0x85, REPORT_ID_DEVICE_CERT,   /*   Report ID (Device Certification) */
	0x09, 0xC5,                    /*   Usage (Vendor Usage 0xC5) */
	0x15, 0x00,                    /*   Logical Minimum (0) */
	0x26, 0xFF, 0x00,              /*   Logical Maximum (255) */
	0x75, 0x08,                    /*   Report Size (8) */
	0x96, 0x00, 0x01,              /*   Report Count (256) */
	0xB1, 0x02,                    /*   Feature (Data,Var,Abs) */

	0xC0,                          /* End Collection */
};

/* A 256-byte default blob for the 'device certification status' feature report.
 *
 * TODO(b/113248108): do we need a real certification?
 */
static const uint8_t device_cert_response[] = {
	REPORT_ID_DEVICE_CERT,

	0xFC, 0x28, 0xFE, 0x84, 0x40, 0xCB, 0x9A, 0x87,
	0x0D, 0xBE, 0x57, 0x3C, 0xB6, 0x70, 0x09, 0x88,
	0x07, 0x97, 0x2D, 0x2B, 0xE3, 0x38, 0x34, 0xB6,
	0x6C, 0xED, 0xB0, 0xF7, 0xE5, 0x9C, 0xF6, 0xC2,
	0x2E, 0x84, 0x1B, 0xE8, 0xB4, 0x51, 0x78, 0x43,
	0x1F, 0x28, 0x4B, 0x7C, 0x2D, 0x53, 0xAF, 0xFC,
	0x47, 0x70, 0x1B, 0x59, 0x6F, 0x74, 0x43, 0xC4,
	0xF3, 0x47, 0x18, 0x53, 0x1A, 0xA2, 0xA1, 0x71,
	0xC7, 0x95, 0x0E, 0x31, 0x55, 0x21, 0xD3, 0xB5,
	0x1E, 0xE9, 0x0C, 0xBA, 0xEC, 0xB8, 0x89, 0x19,
	0x3E, 0xB3, 0xAF, 0x75, 0x81, 0x9D, 0x53, 0xB9,
	0x41, 0x57, 0xF4, 0x6D, 0x39, 0x25, 0x29, 0x7C,
	0x87, 0xD9, 0xB4, 0x98, 0x45, 0x7D, 0xA7, 0x26,
	0x9C, 0x65, 0x3B, 0x85, 0x68, 0x89, 0xD7, 0x3B,
	0xBD, 0xFF, 0x14, 0x67, 0xF2, 0x2B, 0xF0, 0x2A,
	0x41, 0x54, 0xF0, 0xFD, 0x2C, 0x66, 0x7C, 0xF8,
	0xC0, 0x8F, 0x33, 0x13, 0x03, 0xF1, 0xD3, 0xC1,
	0x0B, 0x89, 0xD9, 0x1B, 0x62, 0xCD, 0x51, 0xB7,
	0x80, 0xB8, 0xAF, 0x3A, 0x10, 0xC1, 0x8A, 0x5B,
	0xE8, 0x8A, 0x56, 0xF0, 0x8C, 0xAA, 0xFA, 0x35,
	0xE9, 0x42, 0xC4, 0xD8, 0x55, 0xC3, 0x38, 0xCC,
	0x2B, 0x53, 0x5C, 0x69, 0x52, 0xD5, 0xC8, 0x73,
	0x02, 0x38, 0x7C, 0x73, 0xB6, 0x41, 0xE7, 0xFF,
	0x05, 0xD8, 0x2B, 0x79, 0x9A, 0xE2, 0x34, 0x60,
	0x8F, 0xA3, 0x32, 0x1F, 0x09, 0x78, 0x62, 0xBC,
	0x80, 0xE3, 0x0F, 0xBD, 0x65, 0x20, 0x08, 0x13,
	0xC1, 0xE2, 0xEE, 0x53, 0x2D, 0x86, 0x7E, 0xA7,
	0x5A, 0xC5, 0xD3, 0x7D, 0x98, 0xBE, 0x31, 0x48,
	0x1F, 0xFB, 0xDA, 0xAF, 0xA2, 0xA8, 0x6A, 0x89,
	0xD6, 0xBF, 0xF2, 0xD3, 0x32, 0x2A, 0x9A, 0xE4,
	0xCF, 0x17, 0xB7, 0xB8, 0xF4, 0xE1, 0x33, 0x08,
	0x24, 0x8B, 0xC4, 0x43, 0xA5, 0xE5, 0x24, 0xC2,
};

/* Device capabilities feature report. */
static const uint8_t device_caps_response[] = {
	REPORT_ID_DEVICE_CAPS,

	MAX_FINGERS,            /* Contact Count Maximum */
	0x00,                   /* Pad Type: Depressible click-pad */
};

const struct usb_hid_descriptor USB_CUSTOM_DESC_VAR(USB_IFACE_HID_TOUCHPAD,
						hid, hid_desc_tp) = {
	.bLength = 9,
	.bDescriptorType = USB_HID_DT_HID,
	.bcdHID = 0x0100,
	.bCountryCode = 0x00, /* Hardware target country */
	.bNumDescriptors = 1,
	.desc = {{
		.bDescriptorType = USB_HID_DT_REPORT,
		.wDescriptorLength = sizeof(report_desc)
	}}
};

static usb_uint hid_ep_buf[DIV_ROUND_UP(HID_TOUCHPAD_REPORT_SIZE, 2)] __usb_ram;

/*
 * Write a report to EP, must be called with queue mutex held, and caller
 * must first check that EP is not busy.
 */
static void write_touchpad_report(struct usb_hid_touchpad_report *report)
{
	memcpy_to_usbram((void *) usb_sram_addr(hid_ep_buf),
			 report, sizeof(*report));
	/* enable TX */
	STM32_TOGGLE_EP(USB_EP_HID_TOUCHPAD, EP_TX_MASK, EP_TX_VALID, 0);

	/*
	 * Wake the host. This is required to prevent a race between EP getting
	 * reloaded and host suspending the device, as, ideally, we never want
	 * to have EP loaded during suspend, to avoid reporting stale data.
	 */
	usb_wake();
}

static void hid_touchpad_process_queue(void);
DECLARE_DEFERRED(hid_touchpad_process_queue);

static void hid_touchpad_process_queue(void)
{
	struct usb_hid_touchpad_report report;
	uint16_t now;
	int trimming = 0;

	mutex_lock(&report_queue_mutex);

	/* EP is busy, or nothing in queue: do nothing. */
	if (queue_count(&report_queue) == 0)
		goto unlock;

	now = __hw_clock_source_read() / USB_HID_TOUCHPAD_TIMESTAMP_UNIT;

	if (usb_is_suspended() ||
			(STM32_USB_EP(USB_EP_HID_TOUCHPAD) & EP_TX_MASK)
				== EP_TX_VALID) {
		usb_wake();

		/* Let's trim old events from the queue, if any. */
		trimming = 1;
	} else {
		hook_call_deferred(&hid_touchpad_process_queue_data, -1);
	}

	if (touchpad_debug)
		CPRINTS("TPQ t=%d (%d)", trimming, queue_count(&report_queue));

	while (queue_count(&report_queue) > 0) {
		int delta;

		queue_peek_units(&report_queue, &report, 0, 1);

		delta = (int)((uint16_t)(now - report.timestamp))
				* USB_HID_TOUCHPAD_TIMESTAMP_UNIT;

		if (touchpad_debug)
			CPRINTS("evt t=%d d=%d", report.timestamp, delta);

		/* Drop old events */
		if (delta > EVENT_DISCARD_MAX_TIME) {
			queue_advance_head(&report_queue, 1);
			continue;
		}

		if (trimming) {
			/*
			 * If we stil fail to resume, this will discard the
			 * event after the timeout expires.
			 */
			hook_call_deferred(&hid_touchpad_process_queue_data,
					   EVENT_DISCARD_MAX_TIME - delta);
		} else {
			queue_advance_head(&report_queue, 1);
			write_touchpad_report(&report);
		}
		break;
	}

unlock:
	mutex_unlock(&report_queue_mutex);
}

void set_touchpad_report(struct usb_hid_touchpad_report *report)
{
	static int print_full = 1;

	mutex_lock(&report_queue_mutex);

	/* USB/EP ready and nothing in queue, just write the report. */
	if (!usb_is_suspended() &&
	    (STM32_USB_EP(USB_EP_HID_TOUCHPAD) & EP_TX_MASK) != EP_TX_VALID
	    && queue_count(&report_queue) == 0) {
		write_touchpad_report(report);
		mutex_unlock(&report_queue_mutex);
		return;
	}

	/* Else add to queue, dropping oldest event if needed. */
	if (touchpad_debug)
		CPRINTS("sTP t=%d", report->timestamp);
	if (queue_is_full(&report_queue)) {
		if (print_full)
			CPRINTF("TP queue full\n");
		print_full = 0;

		queue_advance_head(&report_queue, 1);
	} else {
		print_full = 1;
	}
	queue_add_unit(&report_queue, report);

	mutex_unlock(&report_queue_mutex);

	hid_touchpad_process_queue();
}

static void hid_touchpad_tx(void)
{
	hid_tx(USB_EP_HID_TOUCHPAD);

	if (queue_count(&report_queue) > 0)
		hook_call_deferred(&hid_touchpad_process_queue_data, 0);
}

static void hid_touchpad_event(enum usb_ep_event evt)
{
	if (evt == USB_EVENT_RESET)
		hid_reset(USB_EP_HID_TOUCHPAD, hid_ep_buf,
			  HID_TOUCHPAD_REPORT_SIZE, NULL, 0);
	else if (evt == USB_EVENT_DEVICE_RESUME &&
			queue_count(&report_queue) > 0)
		hook_call_deferred(&hid_touchpad_process_queue_data, 0);
}

USB_DECLARE_EP(USB_EP_HID_TOUCHPAD, hid_touchpad_tx, hid_touchpad_tx,
	       hid_touchpad_event);

static int get_report(uint8_t report_id, uint8_t report_type,
		      const uint8_t **buffer_ptr,
		      int *buffer_size)
{
	switch (report_id) {
	case REPORT_ID_DEVICE_CAPS:
		*buffer_ptr = device_caps_response;
		*buffer_size = MIN(sizeof(device_caps_response), *buffer_size);
		return 0;
	case REPORT_ID_DEVICE_CERT:
		*buffer_ptr = device_cert_response;
		*buffer_size = MIN(sizeof(device_cert_response), *buffer_size);
		return 0;
	}
	return -1;
}

static const struct usb_hid_config_t hid_config_tp = {
	.report_desc = report_desc,
	.report_size = sizeof(report_desc),
	.hid_desc = &hid_desc_tp,
	.get_report = get_report,
};

static int hid_touchpad_iface_request(usb_uint *ep0_buf_rx,
				      usb_uint *ep0_buf_tx)
{
	return hid_iface_request(ep0_buf_rx, ep0_buf_tx, &hid_config_tp);
}
USB_DECLARE_IFACE(USB_IFACE_HID_TOUCHPAD, hid_touchpad_iface_request)
