// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2015-2017 Samsung Electronics Co. Ltd.
 *
 * 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.
 */

 /* usb notify layer v3.4 */

#define pr_fmt(fmt) "usb_notify: " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/usb.h>
#include <linux/notifier.h>
#include <linux/version.h>
#include "usb_notify.h"
#include "../core/hub.h"

#define SMARTDOCK_INDEX	1
#define MMDOCK_INDEX	2

struct dev_table {
	struct usb_device_id dev;
	int index;
};

static struct dev_table enable_notify_hub_table[] = {
	{ .dev = { USB_DEVICE(0x0424, 0x2514), },
	   .index = SMARTDOCK_INDEX,
	}, /* SMART DOCK HUB 1 */
	{ .dev = { USB_DEVICE(0x1a40, 0x0101), },
	   .index = SMARTDOCK_INDEX,
	}, /* SMART DOCK HUB 2 */
	{ .dev = { USB_DEVICE(0x0424, 0x9512), },
	   .index = MMDOCK_INDEX,
	}, /* SMSC USB LAN HUB 9512 */
	{}
};

static struct dev_table essential_device_table[] = {
	{ .dev = { USB_DEVICE(0x08bb, 0x2704), },
	   .index = SMARTDOCK_INDEX,
	}, /* TI USB Audio DAC 1 */
	{ .dev = { USB_DEVICE(0x08bb, 0x27c4), },
	   .index = SMARTDOCK_INDEX,
	}, /* TI USB Audio DAC 2 */
	{ .dev = { USB_DEVICE(0x0424, 0xec00), },
	   .index = MMDOCK_INDEX,
	}, /* SMSC LAN Driver */
	{}
};

static struct dev_table update_autotimer_device_table[] = {
	{ .dev = { USB_DEVICE(0x04e8, 0xa500), },
	   .index = 5, /* 5 sec timer */
	}, /* GearVR1 */
	{ .dev = { USB_DEVICE(0x04e8, 0xa501), },
	   .index = 5,
	}, /* GearVR2 */
	{ .dev = { USB_DEVICE(0x04e8, 0xa502), },
	   .index = 5,
	}, /* GearVR3 */
	{}
};

static struct dev_table unsupport_device_table[] = {
	{ .dev = { USB_DEVICE(0x1a0a, 0x0201), },
	},
	{}
};

static int check_essential_device(struct usb_device *dev, int index)
{
	struct dev_table *id;
	int ret = 0;

	/* check VID, PID */
	for (id = essential_device_table; id->dev.match_flags; id++) {
		if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
		(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
		id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) &&
		id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct) &&
		id->index == index) {
			ret = 1;
			break;
		}
	}
	return ret;
}

static int check_gamepad_device(struct usb_device *dev)
{
	int ret = 0;

	pr_info("%s : product=%s\n", __func__, dev->product);

	if (!dev->product)
		return ret;

	if (!strncmp(dev->product, "Gamepad for SAMSUNG", 19))
		ret = 1;

	return ret;
}

static int check_lanhub_device(struct usb_device *dev)
{
	int ret = 0;

	pr_info("%s : product=%s\n", __func__, dev->product);

	if (!dev->product)
		return ret;

	if (!strncmp(dev->product, "LAN9512", 8))
		ret = 1;

	return ret;
}

static int is_notify_hub(struct usb_device *dev)
{
	struct dev_table *id;
	struct usb_device *hdev;
	int ret = 0;

	hdev = dev->parent;
	if (!hdev)
		goto skip;
	/* check VID, PID */
	for (id = enable_notify_hub_table; id->dev.match_flags; id++) {
		if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
		(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
		id->dev.idVendor == le16_to_cpu(hdev->descriptor.idVendor) &&
		id->dev.idProduct == le16_to_cpu(hdev->descriptor.idProduct)) {
			ret = (hdev->parent &&
			(hdev->parent == dev->bus->root_hub)) ? id->index : 0;
			break;
		}
	}
skip:
	return ret;
}

static int get_autosuspend_time(struct usb_device *dev)
{
	struct dev_table *id;
	int ret = 0;

	/* check VID, PID */
	for (id = update_autotimer_device_table; id->dev.match_flags; id++) {
		if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
		(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
		id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) &&
		id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct)) {
			ret = id->index;
			break;
		}
	}
	return ret;
}

static int call_battery_notify(struct usb_device *dev, bool on)
{
	struct usb_device *hdev;
	struct usb_device *udev;
	struct otg_notify *o_notify = get_otg_notify();
	int index = 0;
	int count = 0;
	int port;

	index = is_notify_hub(dev);
	if (!index)
		goto skip;
	if (check_essential_device(dev, index))
		goto skip;

	hdev = dev->parent;
	if (!hdev)
		goto skip;

	usb_hub_for_each_child(hdev, port, udev) {
		if (!check_essential_device(udev, index)) {
			if (!on && (udev == dev))
				continue;
			else
				count++;
		}
	}

	pr_info("%s : VID : 0x%x, PID : 0x%x, on=%d, count=%d\n", __func__,
		dev->descriptor.idVendor, dev->descriptor.idProduct,
			on, count);
	if (on) {
		if (count == 1) {
			if (index == SMARTDOCK_INDEX)
				send_otg_notify(o_notify,
					NOTIFY_EVENT_SMTD_EXT_CURRENT, 1);
			else if (index == MMDOCK_INDEX)
				send_otg_notify(o_notify,
					NOTIFY_EVENT_MMD_EXT_CURRENT, 1);
		}
	} else {
		if (!count) {
			if (index == SMARTDOCK_INDEX)
				send_otg_notify(o_notify,
					NOTIFY_EVENT_SMTD_EXT_CURRENT, 0);
			else if (index == MMDOCK_INDEX)
				send_otg_notify(o_notify,
					NOTIFY_EVENT_MMD_EXT_CURRENT, 0);
		}
	}
skip:
	return 0;
}

static int call_device_notify(struct usb_device *dev, int connect)
{
	struct otg_notify *o_notify = get_otg_notify();

	if (dev->bus->root_hub != dev) {
		if (connect) {
			pr_info("%s device\n", __func__);
			send_otg_notify(o_notify,
				NOTIFY_EVENT_DEVICE_CONNECT, 1);

			if (check_gamepad_device(dev))
				send_otg_notify(o_notify,
					NOTIFY_EVENT_GAMEPAD_CONNECT, 1);
			else if (check_lanhub_device(dev))
				send_otg_notify(o_notify,
					NOTIFY_EVENT_LANHUB_CONNECT, 1);
			else
				;
			store_usblog_notify(NOTIFY_PORT_CONNECT,
				(void *)&dev->descriptor.idVendor,
				(void *)&dev->descriptor.idProduct);
		} else
			store_usblog_notify(NOTIFY_PORT_DISCONNECT,
				(void *)&dev->descriptor.idVendor,
				(void *)&dev->descriptor.idProduct);
	} else {
		if (connect)
			pr_info("%s root hub\n", __func__);
	}

	return 0;
}

static int update_hub_autosuspend_timer(struct usb_device *dev)
{
	struct usb_device *hdev;
	int time = 0;

	if (!dev)
		goto skip;

	hdev = dev->parent;

	if (hdev == NULL || dev->bus->root_hub != hdev)
		goto skip;

	/* hdev is root hub */
	time = get_autosuspend_time(dev);
	if (time == hdev->dev.power.autosuspend_delay)
		goto skip;

	pm_runtime_set_autosuspend_delay(&hdev->dev, time*1000);
	pr_info("set autosuspend delay time=%d sec\n", time);
skip:
	return 0;
}

static void check_device_speed(struct usb_device *dev, bool on)
{
	struct otg_notify *o_notify = get_otg_notify();
	struct usb_device *hdev;
	struct usb_device *udev;
	int port = 0;
	int speed = USB_SPEED_UNKNOWN;
	static int hs_hub;
	static int ss_hub;

	if (!o_notify) {
		pr_err("%s otg_notify is null\n", __func__);
		return;
	}

	hdev = dev->parent;
	if (!hdev)
		return;

	hdev = dev->bus->root_hub;
	if (!hdev)
		return;
	
	usb_hub_for_each_child(hdev, port, udev) {
		if (!on && (udev == dev))
			continue;
		if (udev->speed > speed)
			speed = udev->speed;
	}

	if (hdev->speed >= USB_SPEED_SUPER) {
		if (speed > USB_SPEED_UNKNOWN)
			ss_hub = 1;
		else
			ss_hub = 0;
	} else if (hdev->speed > USB_SPEED_UNKNOWN
			&& hdev->speed != USB_SPEED_WIRELESS) {
		if (speed > USB_SPEED_UNKNOWN)
			hs_hub = 1;
		else
			hs_hub = 0;
	} else
		;

	if (ss_hub || hs_hub) {
		if (speed > o_notify->speed)
			o_notify->speed = speed;
	} else
		o_notify->speed = USB_SPEED_UNKNOWN;

	pr_info("%s : dev->speed %s %s\n", __func__,
		usb_speed_string(dev->speed), on ? "on" : "off");

	pr_info("%s : o_notify->speed %s\n", __func__,
		usb_speed_string(o_notify->speed));
}

#if defined(CONFIG_USB_HW_PARAM)
static int set_hw_param(struct usb_device *dev)
{
	struct otg_notify *o_notify = get_otg_notify();
	int ret = 0;
	int bInterfaceClass = 0, speed = 0;

	if (o_notify == NULL || dev->config->interface[0] == NULL) {
		ret =  -EFAULT;
		goto err;
	}

	if (dev->bus->root_hub != dev) {
		bInterfaceClass
			= dev->config->interface[0]
				->cur_altsetting->desc.bInterfaceClass;
		speed = dev->speed;

		pr_info("%s USB device connected - Class : 0x%x, speed : 0x%x\n",
			__func__, bInterfaceClass, speed);

		if (bInterfaceClass == USB_CLASS_AUDIO)
			inc_hw_param(o_notify, USB_HOST_CLASS_AUDIO_COUNT);
		else if (bInterfaceClass == USB_CLASS_COMM)
			inc_hw_param(o_notify, USB_HOST_CLASS_COMM_COUNT);
		else if (bInterfaceClass == USB_CLASS_HID)
			inc_hw_param(o_notify, USB_HOST_CLASS_HID_COUNT);
		else if (bInterfaceClass == USB_CLASS_PHYSICAL)
			inc_hw_param(o_notify, USB_HOST_CLASS_PHYSICAL_COUNT);
		else if (bInterfaceClass == USB_CLASS_STILL_IMAGE)
			inc_hw_param(o_notify, USB_HOST_CLASS_IMAGE_COUNT);
		else if (bInterfaceClass == USB_CLASS_PRINTER)
			inc_hw_param(o_notify, USB_HOST_CLASS_PRINTER_COUNT);
		else if (bInterfaceClass == USB_CLASS_MASS_STORAGE) {
			inc_hw_param(o_notify, USB_HOST_CLASS_STORAGE_COUNT);
			if (speed == USB_SPEED_SUPER)
				inc_hw_param(o_notify,
					USB_HOST_STORAGE_SUPER_COUNT);
			else if (speed == USB_SPEED_HIGH)
				inc_hw_param(o_notify,
					USB_HOST_STORAGE_HIGH_COUNT);
			else if (speed == USB_SPEED_FULL)
				inc_hw_param(o_notify,
					USB_HOST_STORAGE_FULL_COUNT);
		} else if (bInterfaceClass == USB_CLASS_HUB)
			inc_hw_param(o_notify, USB_HOST_CLASS_HUB_COUNT);
		else if (bInterfaceClass == USB_CLASS_CDC_DATA)
			inc_hw_param(o_notify, USB_HOST_CLASS_CDC_COUNT);
		else if (bInterfaceClass == USB_CLASS_CSCID)
			inc_hw_param(o_notify, USB_HOST_CLASS_CSCID_COUNT);
		else if (bInterfaceClass == USB_CLASS_CONTENT_SEC)
			inc_hw_param(o_notify, USB_HOST_CLASS_CONTENT_COUNT);
		else if (bInterfaceClass == USB_CLASS_VIDEO)
			inc_hw_param(o_notify, USB_HOST_CLASS_VIDEO_COUNT);
		else if (bInterfaceClass == USB_CLASS_WIRELESS_CONTROLLER)
			inc_hw_param(o_notify, USB_HOST_CLASS_WIRELESS_COUNT);
		else if (bInterfaceClass == USB_CLASS_MISC)
			inc_hw_param(o_notify, USB_HOST_CLASS_MISC_COUNT);
		else if (bInterfaceClass == USB_CLASS_APP_SPEC)
			inc_hw_param(o_notify, USB_HOST_CLASS_APP_COUNT);
		else if (bInterfaceClass == USB_CLASS_VENDOR_SPEC)
			inc_hw_param(o_notify, USB_HOST_CLASS_VENDOR_COUNT);

		if (speed == USB_SPEED_SUPER)
			inc_hw_param(o_notify, USB_HOST_SUPER_SPEED_COUNT);
		else if (speed == USB_SPEED_HIGH)
			inc_hw_param(o_notify, USB_HOST_HIGH_SPEED_COUNT);
		else if (speed == USB_SPEED_FULL)
			inc_hw_param(o_notify, USB_HOST_FULL_SPEED_COUNT);
		else if (speed == USB_SPEED_LOW)
			inc_hw_param(o_notify, USB_HOST_LOW_SPEED_COUNT);
	}
err:
	return ret;
}
#endif

static void check_unsupport_device(struct usb_device *dev)
{
	struct dev_table *id;

	/* check VID, PID */
	for (id = unsupport_device_table; id->dev.match_flags; id++) {
		if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
		(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
		id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) &&
		id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct)) {
#if defined(CONFIG_USB_HOST_CERTI)
			send_usb_certi_uevent(USB_CERTI_UNSUPPORT_ACCESSORY);
#endif
			break;
		}
	}
}

static int dev_notify(struct notifier_block *self,
			       unsigned long action, void *dev)
{
	switch (action) {
	case USB_DEVICE_ADD:
		call_device_notify(dev, 1);
		call_battery_notify(dev, 1);
		check_device_speed(dev, 1);
		update_hub_autosuspend_timer(dev);
#if defined(CONFIG_USB_HW_PARAM)
		set_hw_param(dev);
#endif
		check_unsupport_device(dev);
		break;
	case USB_DEVICE_REMOVE:
		call_device_notify(dev, 0);
		call_battery_notify(dev, 0);
		check_device_speed(dev, 0);
		break;
	}
	return NOTIFY_OK;
}

static struct notifier_block dev_nb = {
	.notifier_call = dev_notify,
};

void register_usbdev_notify(void)
{
	usb_register_notify(&dev_nb);
}
EXPORT_SYMBOL(register_usbdev_notify);

void unregister_usbdev_notify(void)
{
	usb_unregister_notify(&dev_nb);
}
EXPORT_SYMBOL(unregister_usbdev_notify);