/*
 * diag.c -- Diag Gadget driver
 *
 * Copyright (c) 2019, The Linux Foundation. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/usb/ch9.h>
#include <linux/usb/composite.h>
#include <linux/usb/gadget.h>
#include <linux/platform_device.h>

#include "u_diag.h"

/*-------------------------------------------------------------------------*/

static const char longname[] = "diag";

USB_GADGET_COMPOSITE_OPTIONS();

#define DRIVER_VENDOR_NUM	0x05C6
#define DRIVER_PRODUCT_NUM	0x9091

/* string IDs are assigned dynamically */

#define STRING_DESCRIPTION_IDX		USB_GADGET_FIRST_AVAIL_IDX

static struct usb_device_descriptor device_desc = {
	.bLength =		USB_DT_DEVICE_SIZE,
	.bDescriptorType =	USB_DT_DEVICE,
	.bcdUSB =		cpu_to_le16(0x0200),
	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
	.idVendor =		cpu_to_le16(DRIVER_VENDOR_NUM),
	.idProduct =		cpu_to_le16(DRIVER_PRODUCT_NUM),
	.bNumConfigurations =	1,
};

static struct usb_string strings_dev[] = {
	[USB_GADGET_MANUFACTURER_IDX].s	= "Qualcomm Technologies",
	[USB_GADGET_PRODUCT_IDX].s	= "Diag Gadget",
	[USB_GADGET_SERIAL_IDX].s	= "",
	{  } /* end of list */
};

static struct usb_gadget_strings stringtab_dev = {
	.language	= 0x0409,	/* en-us */
	.strings	= strings_dev,
};

static struct usb_gadget_strings *dev_strings[] = {
	&stringtab_dev,
	NULL,
};

static struct usb_function_instance *fi_diag;
static struct usb_function *f_diag;

static int diag_gadget_unbind(struct usb_composite_dev *dev)
{
	usb_put_function(f_diag);
	usb_put_function_instance(fi_diag);
	return 0;
}

static struct usb_configuration diag_config = {
	.label		= "diag",
	.bConfigurationValue = 1,
	.bmAttributes	= USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
	.MaxPower	= CONFIG_USB_GADGET_VBUS_DRAW,
};

static int diag_bind_config(struct usb_configuration *c)
{
	int status;

	f_diag = usb_get_function(fi_diag);
	if (IS_ERR(f_diag))
		return PTR_ERR(f_diag);

	status = usb_add_function(c, f_diag);
	if (status < 0) {
		usb_put_function(f_diag);
		return status;
	}

	return 0;
}

static int diag_gadget_bind(struct usb_composite_dev *cdev)
{
	struct diag_opts *diag_opts;
	int status;

	fi_diag = usb_get_function_instance("diag");
	if (IS_ERR(fi_diag))
		return PTR_ERR(fi_diag);

	diag_opts = container_of(fi_diag, struct diag_opts, func_inst);
	fi_diag->set_inst_name(fi_diag, "diag");

	status = usb_string_ids_tab(cdev, strings_dev);
	if (status < 0)
		goto put;

	device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;
	device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;
	diag_config.iConfiguration = strings_dev[STRING_DESCRIPTION_IDX].id;

	status = usb_add_config(cdev, &diag_config, diag_bind_config);
	if (status < 0)
		goto put;

	usb_composite_overwrite_options(cdev, &coverwrite);

	return 0;
put:
	usb_put_function_instance(fi_diag);
	return status;
}

static struct usb_composite_driver diag_gadget_driver = {
	.name		= (char *) longname,
	.dev		= &device_desc,
	.strings	= dev_strings,
	.max_speed	= USB_SPEED_HIGH,
	.bind		= diag_gadget_bind,
	.unbind		= diag_gadget_unbind,
};

static int diag_gadget_probe(struct platform_device *pdev)
{
	usb_composite_probe(&diag_gadget_driver);
	return 0;
}

static int diag_gadget_remove(struct platform_device *pdev)
{
	usb_composite_unregister(&diag_gadget_driver);
	return 0;
}

static const struct of_device_id diag_gadget_table[] = {
	{.compatible = "qcom,gadget-diag"},
	{},
};
MODULE_DEVICE_TABLE(of, diag_gadget_table);

static struct platform_driver diag_gadget_platform_driver = {
	.probe = diag_gadget_probe,
	.remove = diag_gadget_remove,
	.driver = {
		.name = "DIAG Gadget Platform",
		.owner = THIS_MODULE,
		.of_match_table = diag_gadget_table,
	},
};

module_platform_driver(diag_gadget_platform_driver);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Diag Gadget Driver");
MODULE_LICENSE("Dual BSD/GPL");
