// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
 */

#include <linux/usb/usbpd.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/delay.h>

#include "dp_usbpd.h"
#include "dp_debug.h"

/* DP specific VDM commands */
#define DP_USBPD_VDM_STATUS	0x10
#define DP_USBPD_VDM_CONFIGURE	0x11

/* USBPD-TypeC specific Macros */
#define VDM_VERSION		0x0
#define USB_C_DP_SID		0xFF01

enum dp_usbpd_pin_assignment {
	DP_USBPD_PIN_A,
	DP_USBPD_PIN_B,
	DP_USBPD_PIN_C,
	DP_USBPD_PIN_D,
	DP_USBPD_PIN_E,
	DP_USBPD_PIN_F,
	DP_USBPD_PIN_MAX,
};

enum dp_usbpd_events {
	DP_USBPD_EVT_DISCOVER,
	DP_USBPD_EVT_ENTER,
	DP_USBPD_EVT_STATUS,
	DP_USBPD_EVT_CONFIGURE,
	DP_USBPD_EVT_CC_PIN_POLARITY,
	DP_USBPD_EVT_EXIT,
	DP_USBPD_EVT_ATTENTION,
};

enum dp_usbpd_alt_mode {
	DP_USBPD_ALT_MODE_NONE	    = 0,
	DP_USBPD_ALT_MODE_INIT	    = BIT(0),
	DP_USBPD_ALT_MODE_DISCOVER  = BIT(1),
	DP_USBPD_ALT_MODE_ENTER	    = BIT(2),
	DP_USBPD_ALT_MODE_STATUS    = BIT(3),
	DP_USBPD_ALT_MODE_CONFIGURE = BIT(4),
};

struct dp_usbpd_capabilities {
	enum dp_usbpd_port port;
	bool receptacle_state;
	u8 ulink_pin_config;
	u8 dlink_pin_config;
};

struct dp_usbpd_private {
	bool forced_disconnect;
	u32 vdo;
	struct device *dev;
	struct usbpd *pd;
	struct usbpd_svid_handler svid_handler;
	struct dp_hpd_cb *dp_cb;
	struct dp_usbpd_capabilities cap;
	struct dp_usbpd dp_usbpd;
	enum dp_usbpd_alt_mode alt_mode;
	u32 dp_usbpd_config;
};

static const char *dp_usbpd_pin_name(u8 pin)
{
	switch (pin) {
	case DP_USBPD_PIN_A: return "DP_USBPD_PIN_ASSIGNMENT_A";
	case DP_USBPD_PIN_B: return "DP_USBPD_PIN_ASSIGNMENT_B";
	case DP_USBPD_PIN_C: return "DP_USBPD_PIN_ASSIGNMENT_C";
	case DP_USBPD_PIN_D: return "DP_USBPD_PIN_ASSIGNMENT_D";
	case DP_USBPD_PIN_E: return "DP_USBPD_PIN_ASSIGNMENT_E";
	case DP_USBPD_PIN_F: return "DP_USBPD_PIN_ASSIGNMENT_F";
	default: return "UNKNOWN";
	}
}

static const char *dp_usbpd_port_name(enum dp_usbpd_port port)
{
	switch (port) {
	case DP_USBPD_PORT_NONE: return "DP_USBPD_PORT_NONE";
	case DP_USBPD_PORT_UFP_D: return "DP_USBPD_PORT_UFP_D";
	case DP_USBPD_PORT_DFP_D: return "DP_USBPD_PORT_DFP_D";
	case DP_USBPD_PORT_D_UFP_D: return "DP_USBPD_PORT_D_UFP_D";
	default: return "DP_USBPD_PORT_NONE";
	}
}

static const char *dp_usbpd_cmd_name(u8 cmd)
{
	switch (cmd) {
	case USBPD_SVDM_DISCOVER_MODES: return "USBPD_SVDM_DISCOVER_MODES";
	case USBPD_SVDM_ENTER_MODE: return "USBPD_SVDM_ENTER_MODE";
	case USBPD_SVDM_ATTENTION: return "USBPD_SVDM_ATTENTION";
	case DP_USBPD_VDM_STATUS: return "DP_USBPD_VDM_STATUS";
	case DP_USBPD_VDM_CONFIGURE: return "DP_USBPD_VDM_CONFIGURE";
	default: return "DP_USBPD_VDM_ERROR";
	}
}

static void dp_usbpd_init_port(enum dp_usbpd_port *port, u32 in_port)
{
	switch (in_port) {
	case 0:
		*port = DP_USBPD_PORT_NONE;
		break;
	case 1:
		*port = DP_USBPD_PORT_UFP_D;
		break;
	case 2:
		*port = DP_USBPD_PORT_DFP_D;
		break;
	case 3:
		*port = DP_USBPD_PORT_D_UFP_D;
		break;
	default:
		*port = DP_USBPD_PORT_NONE;
	}
	DP_DEBUG("port:%s\n", dp_usbpd_port_name(*port));
}

static void dp_usbpd_get_capabilities(struct dp_usbpd_private *pd)
{
	struct dp_usbpd_capabilities *cap = &pd->cap;
	u32 buf = pd->vdo;
	int port = buf & 0x3;

	cap->receptacle_state = (buf & BIT(6)) ? true : false;
	cap->dlink_pin_config = (buf >> 8) & 0xff;
	cap->ulink_pin_config = (buf >> 16) & 0xff;

	dp_usbpd_init_port(&cap->port, port);
}

static void dp_usbpd_get_status(struct dp_usbpd_private *pd)
{
	struct dp_usbpd *status = &pd->dp_usbpd;
	u32 buf = pd->vdo;
	int port = buf & 0x3;

	status->low_pow_st     = (buf & BIT(2)) ? true : false;
	status->adaptor_dp_en  = (buf & BIT(3)) ? true : false;
	status->base.multi_func = (buf & BIT(4)) ? true : false;
	status->usb_config_req = (buf & BIT(5)) ? true : false;
	status->exit_dp_mode   = (buf & BIT(6)) ? true : false;
	status->base.hpd_high  = (buf & BIT(7)) ? true : false;
	status->base.hpd_irq   = (buf & BIT(8)) ? true : false;

	DP_DEBUG("low_pow_st = %d, adaptor_dp_en = %d, multi_func = %d\n",
			status->low_pow_st, status->adaptor_dp_en,
			status->base.multi_func);
	DP_DEBUG("usb_config_req = %d, exit_dp_mode = %d, hpd_high =%d\n",
			status->usb_config_req,
			status->exit_dp_mode, status->base.hpd_high);
	DP_DEBUG("hpd_irq = %d\n", status->base.hpd_irq);

	dp_usbpd_init_port(&status->port, port);
}

static u32 dp_usbpd_gen_config_pkt(struct dp_usbpd_private *pd)
{
	u8 pin_cfg, pin;
	u32 config = 0;
	const u32 ufp_d_config = 0x2, dp_ver = 0x1;

	if (pd->cap.receptacle_state)
		pin_cfg = pd->cap.ulink_pin_config;
	else
		pin_cfg = pd->cap.dlink_pin_config;

	for (pin = DP_USBPD_PIN_A; pin < DP_USBPD_PIN_MAX; pin++) {
		if (pin_cfg & BIT(pin)) {
			if (pd->dp_usbpd.base.multi_func) {
				if (pin == DP_USBPD_PIN_D)
					break;
			} else {
				break;
			}
		}
	}

	if (pin == DP_USBPD_PIN_MAX)
		pin = DP_USBPD_PIN_C;

	DP_DEBUG("pin assignment: %s\n", dp_usbpd_pin_name(pin));

	config |= BIT(pin) << 8;

	config |= (dp_ver << 2);
	config |= ufp_d_config;

	DP_DEBUG("config = 0x%x\n", config);
	return config;
}

static void dp_usbpd_send_event(struct dp_usbpd_private *pd,
		enum dp_usbpd_events event)
{
	u32 config;

	switch (event) {
	case DP_USBPD_EVT_DISCOVER:
		usbpd_send_svdm(pd->pd, USB_C_DP_SID,
			USBPD_SVDM_DISCOVER_MODES,
			SVDM_CMD_TYPE_INITIATOR, 0x0, 0x0, 0x0);
		break;
	case DP_USBPD_EVT_ENTER:
		usbpd_send_svdm(pd->pd, USB_C_DP_SID,
			USBPD_SVDM_ENTER_MODE,
			SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0);
		break;
	case DP_USBPD_EVT_EXIT:
		usbpd_send_svdm(pd->pd, USB_C_DP_SID,
			USBPD_SVDM_EXIT_MODE,
			SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0);
		break;
	case DP_USBPD_EVT_STATUS:
		config = 0x1; /* DFP_D connected */
		usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_STATUS,
			SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1);
		break;
	case DP_USBPD_EVT_CONFIGURE:
		config = dp_usbpd_gen_config_pkt(pd);
		usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_CONFIGURE,
			SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1);
		break;
	default:
		DP_ERR("unknown event:%d\n", event);
	}
}

static void dp_usbpd_connect_cb(struct usbpd_svid_handler *hdlr,
		bool peer_usb_comm)
{
	struct dp_usbpd_private *pd;

	pd = container_of(hdlr, struct dp_usbpd_private, svid_handler);
	if (!pd) {
		DP_ERR("get_usbpd phandle failed\n");
		return;
	}

	DP_DEBUG("peer_usb_comm: %d\n", peer_usb_comm);
	pd->dp_usbpd.base.peer_usb_comm = peer_usb_comm;
	dp_usbpd_send_event(pd, DP_USBPD_EVT_DISCOVER);
}

static void dp_usbpd_disconnect_cb(struct usbpd_svid_handler *hdlr)
{
	struct dp_usbpd_private *pd;

	pd = container_of(hdlr, struct dp_usbpd_private, svid_handler);
	if (!pd) {
		DP_ERR("get_usbpd phandle failed\n");
		return;
	}

	pd->alt_mode = DP_USBPD_ALT_MODE_NONE;
	pd->dp_usbpd.base.alt_mode_cfg_done = false;
	DP_DEBUG("\n");

	if (pd->dp_cb && pd->dp_cb->disconnect)
		pd->dp_cb->disconnect(pd->dev);
}

static int dp_usbpd_validate_callback(u8 cmd,
	enum usbpd_svdm_cmd_type cmd_type, int num_vdos)
{
	int ret = 0;

	if (cmd_type == SVDM_CMD_TYPE_RESP_NAK) {
		DP_ERR("error: NACK\n");
		ret = -EINVAL;
		goto end;
	}

	if (cmd_type == SVDM_CMD_TYPE_RESP_BUSY) {
		DP_ERR("error: BUSY\n");
		ret = -EBUSY;
		goto end;
	}

	if (cmd == USBPD_SVDM_ATTENTION) {
		if (cmd_type != SVDM_CMD_TYPE_INITIATOR) {
			DP_ERR("error: invalid cmd type for attention\n");
			ret = -EINVAL;
			goto end;
		}

		if (!num_vdos) {
			DP_ERR("error: no vdo provided\n");
			ret = -EINVAL;
			goto end;
		}
	} else {
		if (cmd_type != SVDM_CMD_TYPE_RESP_ACK) {
			DP_ERR("error: invalid cmd type\n");
			ret = -EINVAL;
		}
	}
end:
	return ret;
}


static int dp_usbpd_get_ss_lanes(struct dp_usbpd_private *pd)
{
	int rc = 0;
	int timeout = 250;

	/*
	 * By default, USB reserves two lanes for Super Speed.
	 * Which means DP has remaining two lanes to operate on.
	 * If multi-function is not supported, request USB to
	 * release the Super Speed lanes so that DP can use
	 * all four lanes in case DPCD indicates support for
	 * four lanes.
	 */
	if (!pd->dp_usbpd.base.multi_func) {
		while (timeout) {
			rc = pd->svid_handler.request_usb_ss_lane(
					pd->pd, &pd->svid_handler);
			if (rc != -EBUSY)
				break;

			DP_WARN("USB busy, retry\n");

			/* wait for hw recommended delay for usb */
			msleep(20);
			timeout--;
		}
	}

	return rc;
}

static void dp_usbpd_response_cb(struct usbpd_svid_handler *hdlr, u8 cmd,
				enum usbpd_svdm_cmd_type cmd_type,
				const u32 *vdos, int num_vdos)
{
	struct dp_usbpd_private *pd;
	int rc = 0;

	pd = container_of(hdlr, struct dp_usbpd_private, svid_handler);

	DP_DEBUG("callback -> cmd: %s, *vdos = 0x%x, num_vdos = %d\n",
				dp_usbpd_cmd_name(cmd), *vdos, num_vdos);

	if (dp_usbpd_validate_callback(cmd, cmd_type, num_vdos)) {
		DP_DEBUG("invalid callback received\n");
		return;
	}

	switch (cmd) {
	case USBPD_SVDM_DISCOVER_MODES:
		pd->vdo = *vdos;
		dp_usbpd_get_capabilities(pd);

		pd->alt_mode |= DP_USBPD_ALT_MODE_DISCOVER;

		if (pd->cap.port & BIT(0))
			dp_usbpd_send_event(pd, DP_USBPD_EVT_ENTER);
		break;
	case USBPD_SVDM_ENTER_MODE:
		pd->alt_mode |= DP_USBPD_ALT_MODE_ENTER;

		dp_usbpd_send_event(pd, DP_USBPD_EVT_STATUS);
		break;
	case USBPD_SVDM_ATTENTION:
		if (pd->forced_disconnect)
			break;

		pd->vdo = *vdos;
		dp_usbpd_get_status(pd);

		if (!pd->dp_usbpd.base.alt_mode_cfg_done) {
			if (pd->dp_usbpd.port & BIT(1))
				dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE);
			break;
		}

		if (pd->dp_cb && pd->dp_cb->attention)
			pd->dp_cb->attention(pd->dev);

		break;
	case DP_USBPD_VDM_STATUS:
		pd->vdo = *vdos;
		dp_usbpd_get_status(pd);

		if (!(pd->alt_mode & DP_USBPD_ALT_MODE_CONFIGURE)) {
			pd->alt_mode |= DP_USBPD_ALT_MODE_STATUS;

			if (pd->dp_usbpd.port & BIT(1))
				dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE);
		}
		break;
	case DP_USBPD_VDM_CONFIGURE:
		pd->alt_mode |= DP_USBPD_ALT_MODE_CONFIGURE;
		pd->dp_usbpd.base.alt_mode_cfg_done = true;
		pd->forced_disconnect = false;
		dp_usbpd_get_status(pd);

		pd->dp_usbpd.base.orientation =
			usbpd_get_plug_orientation(pd->pd);

		rc = dp_usbpd_get_ss_lanes(pd);
		if (rc) {
			DP_ERR("failed to get SuperSpeed lanes\n");
			break;
		}

		if (pd->dp_cb && pd->dp_cb->configure)
			pd->dp_cb->configure(pd->dev);
		break;
	default:
		DP_ERR("unknown cmd: %d\n", cmd);
		break;
	}
}

static int dp_usbpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd)
{
	int rc = 0;
	struct dp_usbpd *dp_usbpd;
	struct dp_usbpd_private *pd;

	if (!dp_hpd) {
		DP_ERR("invalid input\n");
		rc = -EINVAL;
		goto error;
	}

	dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
	pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);

	dp_usbpd->base.hpd_high = hpd;
	pd->forced_disconnect = !hpd;
	pd->dp_usbpd.base.alt_mode_cfg_done = hpd;

	DP_DEBUG("hpd_high=%d, forced_disconnect=%d, orientation=%d\n",
			dp_usbpd->base.hpd_high, pd->forced_disconnect,
			pd->dp_usbpd.base.orientation);
	if (hpd)
		pd->dp_cb->configure(pd->dev);
	else
		pd->dp_cb->disconnect(pd->dev);

error:
	return rc;
}

static int dp_usbpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo)
{
	int rc = 0;
	struct dp_usbpd *dp_usbpd;
	struct dp_usbpd_private *pd;

	dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
	if (!dp_usbpd) {
		DP_ERR("invalid input\n");
		rc = -EINVAL;
		goto error;
	}

	pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);

	pd->vdo = vdo;
	dp_usbpd_get_status(pd);

	if (pd->dp_cb && pd->dp_cb->attention)
		pd->dp_cb->attention(pd->dev);
error:
	return rc;
}

int dp_usbpd_register(struct dp_hpd *dp_hpd)
{
	struct dp_usbpd *dp_usbpd;
	struct dp_usbpd_private *usbpd;
	int rc = 0;

	if (!dp_hpd)
		return -EINVAL;

	dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);

	usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);

	rc = usbpd_register_svid(usbpd->pd, &usbpd->svid_handler);
	if (rc)
		DP_ERR("pd registration failed\n");

	return rc;
}

static void dp_usbpd_wakeup_phy(struct dp_hpd *dp_hpd, bool wakeup)
{
	struct dp_usbpd *dp_usbpd;
	struct dp_usbpd_private *usbpd;

	dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
	usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);

	if (!usbpd->pd) {
		DP_ERR("usbpd pointer invalid");
		return;
	}

	usbpd_vdm_in_suspend(usbpd->pd, wakeup);
}

struct dp_hpd *dp_usbpd_get(struct device *dev, struct dp_hpd_cb *cb)
{
	int rc = 0;
	const char *pd_phandle = "qcom,dp-usbpd-detection";
	struct usbpd *pd = NULL;
	struct dp_usbpd_private *usbpd;
	struct dp_usbpd *dp_usbpd;
	struct usbpd_svid_handler svid_handler = {
		.svid		= USB_C_DP_SID,
		.vdm_received	= NULL,
		.connect	= &dp_usbpd_connect_cb,
		.svdm_received	= &dp_usbpd_response_cb,
		.disconnect	= &dp_usbpd_disconnect_cb,
	};

	if (!cb) {
		DP_ERR("invalid cb data\n");
		rc = -EINVAL;
		goto error;
	}

	pd = devm_usbpd_get_by_phandle(dev, pd_phandle);
	if (IS_ERR(pd)) {
		DP_ERR("usbpd phandle failed (%ld)\n", PTR_ERR(pd));
		rc = PTR_ERR(pd);
		goto error;
	}

	usbpd = devm_kzalloc(dev, sizeof(*usbpd), GFP_KERNEL);
	if (!usbpd) {
		rc = -ENOMEM;
		goto error;
	}

	usbpd->dev = dev;
	usbpd->pd = pd;
	usbpd->svid_handler = svid_handler;
	usbpd->dp_cb = cb;

	dp_usbpd = &usbpd->dp_usbpd;
	dp_usbpd->base.simulate_connect = dp_usbpd_simulate_connect;
	dp_usbpd->base.simulate_attention = dp_usbpd_simulate_attention;
	dp_usbpd->base.register_hpd = dp_usbpd_register;
	dp_usbpd->base.wakeup_phy = dp_usbpd_wakeup_phy;

	return &dp_usbpd->base;
error:
	return ERR_PTR(rc);
}

void dp_usbpd_put(struct dp_hpd *dp_hpd)
{
	struct dp_usbpd *dp_usbpd;
	struct dp_usbpd_private *usbpd;

	dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
	if (!dp_usbpd)
		return;

	usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);

	usbpd_unregister_svid(usbpd->pd, &usbpd->svid_handler);

	devm_kfree(usbpd->dev, usbpd);
}
