// SPDX-License-Identifier: GPL-2.0+
#include "dwc2.h"

static struct descriptor {
	struct usb_hub_descriptor hub;
	struct usb_device_descriptor device;
	struct usb_config_descriptor config;
	struct usb_interface_descriptor interface;
	struct usb_endpoint_descriptor endpoint;
}  __packed descriptor = {
	.hub = {
		.bLength		= USB_DT_HUB_NONVAR_SIZE +
					  ((USB_MAXCHILDREN + 1 + 7) / 8),
		.bDescriptorType	= USB_DT_HUB,
		.bNbrPorts		= 1,
		.wHubCharacteristics	= 0,
		.bPwrOn2PwrGood		= 0,
		.bHubContrCurrent	= 0,
		.u.hs.DeviceRemovable	= {0xff},
		.u.hs.PortPwrCtrlMask	= {}
	},
	.device = {
		.bLength		= USB_DT_DEVICE_SIZE,
		.bDescriptorType	= USB_DT_DEVICE,
		.bcdUSB			= cpu_to_le16(2), /* v2.0 */
		.bDeviceClass		= USB_CLASS_HUB,
		.bDeviceSubClass	= 0,
		.bDeviceProtocol	= USB_HUB_PR_HS_NO_TT,
		.bMaxPacketSize0	= 64,
		.idVendor		= 0x0000,
		.idProduct		= 0x0000,
		.bcdDevice		= 0x0000,
		.iManufacturer		= 1,
		.iProduct		= 2,
		.iSerialNumber		= 0,
		.bNumConfigurations	= 1
	},
	.config = {
		.bLength		= USB_DT_CONFIG_SIZE,
		.bDescriptorType	= USB_DT_CONFIG,
		.wTotalLength		= cpu_to_le16(
						USB_DT_CONFIG_SIZE +
						USB_DT_INTERFACE_SIZE +
						USB_DT_ENDPOINT_SIZE),
		.bNumInterfaces		= 1,
		.bConfigurationValue	= 1,
		.iConfiguration		= 0,
		.bmAttributes		= USB_CONFIG_ATT_SELFPOWER,
		.bMaxPower		= 0
	},
	.interface = {
		.bLength		= USB_DT_INTERFACE_SIZE,
		.bDescriptorType	= USB_DT_INTERFACE,
		.bInterfaceNumber	= 0,
		.bAlternateSetting	= 0,
		.bNumEndpoints		= 1,
		.bInterfaceClass	= USB_CLASS_HUB,
		.bInterfaceSubClass	= 0,
		.bInterfaceProtocol	= 0,
		.iInterface		= 0
	},
	.endpoint = {
		.bLength		= USB_DT_ENDPOINT_SIZE,
		.bDescriptorType	= USB_DT_ENDPOINT,
		.bEndpointAddress	= 1 | USB_DIR_IN, /* 0x81 */
		.bmAttributes		= USB_ENDPOINT_XFER_INT,
		.wMaxPacketSize		= cpu_to_le16(
						(USB_MAXCHILDREN + 1 + 7) / 8),
		.bInterval		= 255
	},
};

static int dwc2_get_port_status(struct dwc2 *dwc2, struct usb_device *dev,
		void *buf, int len)
{
	struct usb_port_status *portsts;
	uint32_t hprt0;
	uint32_t status = 0;
	uint32_t change = 0;
	int speed;

	if (!buf || len < sizeof(*portsts))
		return -1;

	hprt0 = dwc2_readl(dwc2, HPRT0);

	if (hprt0 & HPRT0_CONNSTS)
		status |= USB_PORT_STAT_CONNECTION;
	if (hprt0 & HPRT0_ENA)
		status |= USB_PORT_STAT_ENABLE;
	if (hprt0 & HPRT0_SUSP)
		status |= USB_PORT_STAT_SUSPEND;
	if (hprt0 & HPRT0_OVRCURRACT)
		status |= USB_PORT_STAT_OVERCURRENT;
	if (hprt0 & HPRT0_RST)
		status |= USB_PORT_STAT_RESET;
	if (hprt0 & HPRT0_PWR)
		status |= USB_PORT_STAT_POWER;

	speed = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT;
	if (speed == HPRT0_SPD_HIGH_SPEED)
		status |= USB_PORT_STAT_HIGH_SPEED;
	else if (speed == HPRT0_SPD_LOW_SPEED)
		status |= USB_PORT_STAT_LOW_SPEED;

	if (hprt0 & HPRT0_ENACHG)
		change |= USB_PORT_STAT_C_ENABLE;
	if (hprt0 & HPRT0_CONNDET)
		change |= USB_PORT_STAT_C_CONNECTION;
	if (hprt0 & HPRT0_OVRCURRCHG)
		change |= USB_PORT_STAT_C_OVERCURRENT;

	portsts = buf;
	portsts->wPortStatus = cpu_to_le16(status);
	portsts->wPortChange = cpu_to_le16(change);

	dev->act_len = sizeof(*portsts);
	dev->status = 0;

	return 0;
}

static int dwc2_get_hub_status(struct dwc2 *dwc2, struct usb_device *dev,
		void *buf, int len)
{
	if (!buf || len < 4)
		return -1;

	*(uint32_t *)buf = 0;
	dev->act_len = 4;
	dev->status  = 0;

	return 0;
}

static int dwc2_get_hub_descriptor(struct dwc2 *dwc2, struct usb_device *dev,
		void *buf, int len)
{
	if (!buf)
		return -1;

	dev->act_len = min_t(int, len, descriptor.hub.bLength);
	dev->status = 0;
	memcpy(buf, &descriptor.hub, dev->act_len);

	return 0;
}

static void strle16(__le16 *dest, char *src, size_t n)
{
	unsigned int i;

	for (i = 0; i < n && *src != '\0'; i++, src++)
		dest[i] = cpu_to_le16(*src);
}

static int dwc2_get_string_descriptor(struct dwc2 *dwc2, struct usb_device *dev,
			       void *buf, int len, int index)
{
	char *src, *str = buf;
	__le16 *le16 = (__le16 *)(str + 2);
	int size;

	if (!buf || len < 2)
		return -1;

	switch (index) {
	case 0: /* Language */
		src = "\x09\x04";
		size = strlen(src) + 2;
		len = min_t(int, len, size);

		str[0] = size;
		str[1] = 0x03;
		memcpy(str + 2, src, len - 2);
		break;
	case 1: /* Vendor */
		src = "u-boot";
		size = 2 * strlen(src) + 2;
		len = min_t(int, len, size);

		str[0] = size;
		str[1] = 0x03;
		strle16(le16, src, (len - 2) / 2);
		break;
	case 2: /* Product */
		src = "DWC2 root hub";
		size = 2 * strlen(src) + 2;
		len = min_t(int, len, size);

		str[0] = size;
		str[1] = 0x03;
		strle16(le16, src, (len - 2) / 2);
		break;
	default:
		dwc2_err(dwc2, "roothub: unknown string descriptor: 0x%x\n",
				index);
		return -1;
	}

	dev->act_len = len;
	dev->status = 0;

	return 0;
}

static int dwc2_get_descriptor(struct dwc2 *dwc2, struct usb_device *dev,
			       void *buf, int len, int value)
{
	int index = value >> 8;

	if (!buf || len < 0)
		return -1;

	switch (index) {
	case USB_DT_DEVICE:
		len = min(len, (int)descriptor.device.bLength);
		memcpy(buf, &descriptor.device, len);
		break;
	case USB_DT_CONFIG:
		len = min(len, (int)descriptor.config.wTotalLength);
		memcpy(buf, &descriptor.config, len);
		break;
	case USB_DT_STRING:
		value &= 0xff;
		return dwc2_get_string_descriptor(dwc2, dev, buf, len, value);
	default:
		dwc2_err(dwc2, "roothub: unknown descriptor: 0x%x\n", index);
		return -1;
	}

	dev->act_len = len;
	dev->status = 0;

	return 0;
}

static int dwc2_set_port_feature(struct dwc2 *dwc2, struct usb_device *dev,
		int feature)
{
	uint32_t hprt0;

	hprt0 = dwc2_readl(dwc2, HPRT0);
	hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG);

	switch (feature) {
	case USB_PORT_FEAT_SUSPEND:
		break;
	case USB_PORT_FEAT_RESET:
		hprt0 |= HPRT0_RST;
		dwc2_writel(dwc2, hprt0, HPRT0);

		mdelay(60);

		hprt0 = dwc2_readl(dwc2, HPRT0);
		hprt0 &= ~HPRT0_RST;
		dwc2_writel(dwc2, hprt0, HPRT0);
		break;
	case USB_PORT_FEAT_POWER:
		break;
	case USB_PORT_FEAT_ENABLE:
		/* Set by the core after a reset */
		break;
	default:
		dwc2_dbg(dwc2, "roothub: unsupported set port feature 0x%x\n",
				feature);
		return -1;
	}

	dev->act_len = 0;
	dev->status = 0;

	return 0;
}

static int dwc2_clear_port_feature(struct dwc2 *dwc2, struct usb_device *dev,
		int feature)
{
	uint32_t hprt0;

	hprt0 = dwc2_readl(dwc2, HPRT0);
	hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG);

	switch (feature) {
	case USB_PORT_FEAT_ENABLE:
		hprt0 |= HPRT0_ENA;
		break;
	case USB_PORT_FEAT_SUSPEND:
		break;
	case USB_PORT_FEAT_POWER:
		break;
	case USB_PORT_FEAT_C_CONNECTION:
		hprt0 |= HPRT0_CONNDET;
		break;
	case USB_PORT_FEAT_C_ENABLE:
		hprt0 |= HPRT0_ENACHG;
		break;
	case USB_PORT_FEAT_C_OVER_CURRENT:
		hprt0 |= HPRT0_OVRCURRCHG;
		break;
	default:
		dwc2_dbg(dwc2, "roothub: unsupported clear port feature 0x%x\n",
				feature);
		return -1;
	}

	dwc2_writel(dwc2, hprt0, HPRT0);

	dev->act_len = 0;
	dev->status = 0;

	return 0;
}

static int dwc2_set_address(struct dwc2 *dwc2, struct usb_device *dev, int addr)
{
	dwc2_dbg(dwc2, "roothub: set address to %d\n", addr);
	dwc2->root_hub_devnum = addr;

	dev->act_len = 0;
	dev->status = 0;

	return 0;
}

int dwc2_submit_roothub(struct dwc2 *dwc2, struct usb_device *dev,
		unsigned long pipe, void *buf, int len,
		struct devrequest *setup)
{
	unsigned char reqtype = setup->requesttype;
	unsigned char request = setup->request;
	unsigned short value = le16_to_cpu(setup->value);
	unsigned short size  = le16_to_cpu(setup->length);
	int minlen = min_t(int, len, size);

	if (usb_pipeint(pipe)) {
		dwc2_err(dwc2, "roothub: submit IRQ NOT implemented\n");
		return 0;
	}

	dev->act_len = 0;
	dev->status = USB_ST_STALLED;

#define REQ(l, u) ((l) | ((u) << 8))

	switch (REQ(request, reqtype)) {
	case REQ(USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB):
		return dwc2_get_hub_descriptor(dwc2, dev, buf, minlen);

	case REQ(USB_REQ_GET_DESCRIPTOR, USB_DIR_IN):
		return dwc2_get_descriptor(dwc2, dev, buf, minlen, value);

	case REQ(USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB):
		return dwc2_get_hub_status(dwc2, dev, buf, len);

	case REQ(USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT):
		return dwc2_get_port_status(dwc2, dev, buf, len);

	case REQ(USB_REQ_SET_FEATURE, USB_DIR_OUT | USB_RT_PORT):
		return dwc2_set_port_feature(dwc2, dev, value);

	case REQ(USB_REQ_CLEAR_FEATURE, USB_DIR_OUT | USB_RT_PORT):
		return dwc2_clear_port_feature(dwc2, dev, value);

	case REQ(USB_REQ_SET_ADDRESS, USB_DIR_OUT):
		return dwc2_set_address(dwc2, dev, value);

	case REQ(USB_REQ_SET_CONFIGURATION, USB_DIR_OUT):
		dev->act_len = 0;
		dev->status = 0;
		return 0;

	case REQ(USB_REQ_GET_CONFIGURATION, USB_DIR_IN):
		*(char *)buf = 1;
		dev->act_len = 1;
		dev->status = 0;
		return 0;
	}

	dwc2_err(dwc2, "roothub: unsupported request 0x%x requesttype 0x%x\n",
		 request, reqtype);

	return 0;
}
