162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Tahvo USB transceiver driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2005-2006 Nokia Corporation
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Parts copied from isp1301_omap.c.
862306a36Sopenharmony_ci * Copyright (C) 2004 Texas Instruments
962306a36Sopenharmony_ci * Copyright (C) 2004 David Brownell
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs.
1262306a36Sopenharmony_ci * Modified for Retu/Tahvo MFD by Aaro Koskinen.
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/io.h>
1662306a36Sopenharmony_ci#include <linux/clk.h>
1762306a36Sopenharmony_ci#include <linux/usb.h>
1862306a36Sopenharmony_ci#include <linux/extcon-provider.h>
1962306a36Sopenharmony_ci#include <linux/kernel.h>
2062306a36Sopenharmony_ci#include <linux/module.h>
2162306a36Sopenharmony_ci#include <linux/usb/otg.h>
2262306a36Sopenharmony_ci#include <linux/mfd/retu.h>
2362306a36Sopenharmony_ci#include <linux/usb/gadget.h>
2462306a36Sopenharmony_ci#include <linux/platform_device.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define DRIVER_NAME     "tahvo-usb"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define TAHVO_REG_IDSR	0x02
2962306a36Sopenharmony_ci#define TAHVO_REG_USBR	0x06
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define USBR_SLAVE_CONTROL	(1 << 8)
3262306a36Sopenharmony_ci#define USBR_VPPVIO_SW		(1 << 7)
3362306a36Sopenharmony_ci#define USBR_SPEED		(1 << 6)
3462306a36Sopenharmony_ci#define USBR_REGOUT		(1 << 5)
3562306a36Sopenharmony_ci#define USBR_MASTER_SW2		(1 << 4)
3662306a36Sopenharmony_ci#define USBR_MASTER_SW1		(1 << 3)
3762306a36Sopenharmony_ci#define USBR_SLAVE_SW		(1 << 2)
3862306a36Sopenharmony_ci#define USBR_NSUSPEND		(1 << 1)
3962306a36Sopenharmony_ci#define USBR_SEMODE		(1 << 0)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define TAHVO_MODE_HOST		0
4262306a36Sopenharmony_ci#define TAHVO_MODE_PERIPHERAL	1
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistruct tahvo_usb {
4562306a36Sopenharmony_ci	struct platform_device	*pt_dev;
4662306a36Sopenharmony_ci	struct usb_phy		phy;
4762306a36Sopenharmony_ci	int			vbus_state;
4862306a36Sopenharmony_ci	struct mutex		serialize;
4962306a36Sopenharmony_ci	struct clk		*ick;
5062306a36Sopenharmony_ci	int			irq;
5162306a36Sopenharmony_ci	int			tahvo_mode;
5262306a36Sopenharmony_ci	struct extcon_dev	*extcon;
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic const unsigned int tahvo_cable[] = {
5662306a36Sopenharmony_ci	EXTCON_USB,
5762306a36Sopenharmony_ci	EXTCON_USB_HOST,
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	EXTCON_NONE,
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic ssize_t vbus_show(struct device *device,
6362306a36Sopenharmony_ci			       struct device_attribute *attr, char *buf)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	struct tahvo_usb *tu = dev_get_drvdata(device);
6662306a36Sopenharmony_ci	return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off");
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_cistatic DEVICE_ATTR_RO(vbus);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic void check_vbus_state(struct tahvo_usb *tu)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
7362306a36Sopenharmony_ci	int reg, prev_state;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	reg = retu_read(rdev, TAHVO_REG_IDSR);
7662306a36Sopenharmony_ci	if (reg & TAHVO_STAT_VBUS) {
7762306a36Sopenharmony_ci		switch (tu->phy.otg->state) {
7862306a36Sopenharmony_ci		case OTG_STATE_B_IDLE:
7962306a36Sopenharmony_ci			/* Enable the gadget driver */
8062306a36Sopenharmony_ci			if (tu->phy.otg->gadget)
8162306a36Sopenharmony_ci				usb_gadget_vbus_connect(tu->phy.otg->gadget);
8262306a36Sopenharmony_ci			tu->phy.otg->state = OTG_STATE_B_PERIPHERAL;
8362306a36Sopenharmony_ci			usb_phy_set_event(&tu->phy, USB_EVENT_ENUMERATED);
8462306a36Sopenharmony_ci			break;
8562306a36Sopenharmony_ci		case OTG_STATE_A_IDLE:
8662306a36Sopenharmony_ci			/*
8762306a36Sopenharmony_ci			 * Session is now valid assuming the USB hub is driving
8862306a36Sopenharmony_ci			 * Vbus.
8962306a36Sopenharmony_ci			 */
9062306a36Sopenharmony_ci			tu->phy.otg->state = OTG_STATE_A_HOST;
9162306a36Sopenharmony_ci			break;
9262306a36Sopenharmony_ci		default:
9362306a36Sopenharmony_ci			break;
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci		dev_info(&tu->pt_dev->dev, "USB cable connected\n");
9662306a36Sopenharmony_ci	} else {
9762306a36Sopenharmony_ci		switch (tu->phy.otg->state) {
9862306a36Sopenharmony_ci		case OTG_STATE_B_PERIPHERAL:
9962306a36Sopenharmony_ci			if (tu->phy.otg->gadget)
10062306a36Sopenharmony_ci				usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
10162306a36Sopenharmony_ci			tu->phy.otg->state = OTG_STATE_B_IDLE;
10262306a36Sopenharmony_ci			usb_phy_set_event(&tu->phy, USB_EVENT_NONE);
10362306a36Sopenharmony_ci			break;
10462306a36Sopenharmony_ci		case OTG_STATE_A_HOST:
10562306a36Sopenharmony_ci			tu->phy.otg->state = OTG_STATE_A_IDLE;
10662306a36Sopenharmony_ci			break;
10762306a36Sopenharmony_ci		default:
10862306a36Sopenharmony_ci			break;
10962306a36Sopenharmony_ci		}
11062306a36Sopenharmony_ci		dev_info(&tu->pt_dev->dev, "USB cable disconnected\n");
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	prev_state = tu->vbus_state;
11462306a36Sopenharmony_ci	tu->vbus_state = reg & TAHVO_STAT_VBUS;
11562306a36Sopenharmony_ci	if (prev_state != tu->vbus_state) {
11662306a36Sopenharmony_ci		extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
11762306a36Sopenharmony_ci		sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state");
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic void tahvo_usb_become_host(struct tahvo_usb *tu)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, true);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* Power up the transceiver in USB host mode */
12862306a36Sopenharmony_ci	retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND |
12962306a36Sopenharmony_ci		   USBR_MASTER_SW2 | USBR_MASTER_SW1);
13062306a36Sopenharmony_ci	tu->phy.otg->state = OTG_STATE_A_IDLE;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	check_vbus_state(tu);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic void tahvo_usb_stop_host(struct tahvo_usb *tu)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	tu->phy.otg->state = OTG_STATE_A_IDLE;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic void tahvo_usb_become_peripheral(struct tahvo_usb *tu)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, false);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* Power up transceiver and set it in USB peripheral mode */
14762306a36Sopenharmony_ci	retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT |
14862306a36Sopenharmony_ci		   USBR_NSUSPEND | USBR_SLAVE_SW);
14962306a36Sopenharmony_ci	tu->phy.otg->state = OTG_STATE_B_IDLE;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	check_vbus_state(tu);
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic void tahvo_usb_stop_peripheral(struct tahvo_usb *tu)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	if (tu->phy.otg->gadget)
15762306a36Sopenharmony_ci		usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
15862306a36Sopenharmony_ci	tu->phy.otg->state = OTG_STATE_B_IDLE;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic void tahvo_usb_power_off(struct tahvo_usb *tu)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* Disable gadget controller if any */
16662306a36Sopenharmony_ci	if (tu->phy.otg->gadget)
16762306a36Sopenharmony_ci		usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	/* Power off transceiver */
17062306a36Sopenharmony_ci	retu_write(rdev, TAHVO_REG_USBR, 0);
17162306a36Sopenharmony_ci	tu->phy.otg->state = OTG_STATE_UNDEFINED;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy);
17762306a36Sopenharmony_ci	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
17862306a36Sopenharmony_ci	u16 w;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	dev_dbg(&tu->pt_dev->dev, "%s\n", __func__);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	w = retu_read(rdev, TAHVO_REG_USBR);
18362306a36Sopenharmony_ci	if (suspend)
18462306a36Sopenharmony_ci		w &= ~USBR_NSUSPEND;
18562306a36Sopenharmony_ci	else
18662306a36Sopenharmony_ci		w |= USBR_NSUSPEND;
18762306a36Sopenharmony_ci	retu_write(rdev, TAHVO_REG_USBR, w);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
19562306a36Sopenharmony_ci					    phy);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	mutex_lock(&tu->serialize);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	if (host == NULL) {
20062306a36Sopenharmony_ci		if (tu->tahvo_mode == TAHVO_MODE_HOST)
20162306a36Sopenharmony_ci			tahvo_usb_power_off(tu);
20262306a36Sopenharmony_ci		otg->host = NULL;
20362306a36Sopenharmony_ci		mutex_unlock(&tu->serialize);
20462306a36Sopenharmony_ci		return 0;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (tu->tahvo_mode == TAHVO_MODE_HOST) {
20862306a36Sopenharmony_ci		otg->host = NULL;
20962306a36Sopenharmony_ci		tahvo_usb_become_host(tu);
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	otg->host = host;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	mutex_unlock(&tu->serialize);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	return 0;
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic int tahvo_usb_set_peripheral(struct usb_otg *otg,
22062306a36Sopenharmony_ci				    struct usb_gadget *gadget)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
22362306a36Sopenharmony_ci					    phy);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	mutex_lock(&tu->serialize);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (!gadget) {
22862306a36Sopenharmony_ci		if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
22962306a36Sopenharmony_ci			tahvo_usb_power_off(tu);
23062306a36Sopenharmony_ci		tu->phy.otg->gadget = NULL;
23162306a36Sopenharmony_ci		mutex_unlock(&tu->serialize);
23262306a36Sopenharmony_ci		return 0;
23362306a36Sopenharmony_ci	}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	tu->phy.otg->gadget = gadget;
23662306a36Sopenharmony_ci	if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
23762306a36Sopenharmony_ci		tahvo_usb_become_peripheral(tu);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	mutex_unlock(&tu->serialize);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	return 0;
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	struct tahvo_usb *tu = _tu;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	mutex_lock(&tu->serialize);
24962306a36Sopenharmony_ci	check_vbus_state(tu);
25062306a36Sopenharmony_ci	mutex_unlock(&tu->serialize);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return IRQ_HANDLED;
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic ssize_t otg_mode_show(struct device *device,
25662306a36Sopenharmony_ci			     struct device_attribute *attr, char *buf)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	struct tahvo_usb *tu = dev_get_drvdata(device);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	switch (tu->tahvo_mode) {
26162306a36Sopenharmony_ci	case TAHVO_MODE_HOST:
26262306a36Sopenharmony_ci		return sprintf(buf, "host\n");
26362306a36Sopenharmony_ci	case TAHVO_MODE_PERIPHERAL:
26462306a36Sopenharmony_ci		return sprintf(buf, "peripheral\n");
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	return -EINVAL;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic ssize_t otg_mode_store(struct device *device,
27162306a36Sopenharmony_ci			      struct device_attribute *attr,
27262306a36Sopenharmony_ci			      const char *buf, size_t count)
27362306a36Sopenharmony_ci{
27462306a36Sopenharmony_ci	struct tahvo_usb *tu = dev_get_drvdata(device);
27562306a36Sopenharmony_ci	int r;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	mutex_lock(&tu->serialize);
27862306a36Sopenharmony_ci	if (count >= 4 && strncmp(buf, "host", 4) == 0) {
27962306a36Sopenharmony_ci		if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
28062306a36Sopenharmony_ci			tahvo_usb_stop_peripheral(tu);
28162306a36Sopenharmony_ci		tu->tahvo_mode = TAHVO_MODE_HOST;
28262306a36Sopenharmony_ci		if (tu->phy.otg->host) {
28362306a36Sopenharmony_ci			dev_info(device, "HOST mode: host controller present\n");
28462306a36Sopenharmony_ci			tahvo_usb_become_host(tu);
28562306a36Sopenharmony_ci		} else {
28662306a36Sopenharmony_ci			dev_info(device, "HOST mode: no host controller, powering off\n");
28762306a36Sopenharmony_ci			tahvo_usb_power_off(tu);
28862306a36Sopenharmony_ci		}
28962306a36Sopenharmony_ci		r = strlen(buf);
29062306a36Sopenharmony_ci	} else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) {
29162306a36Sopenharmony_ci		if (tu->tahvo_mode == TAHVO_MODE_HOST)
29262306a36Sopenharmony_ci			tahvo_usb_stop_host(tu);
29362306a36Sopenharmony_ci		tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
29462306a36Sopenharmony_ci		if (tu->phy.otg->gadget) {
29562306a36Sopenharmony_ci			dev_info(device, "PERIPHERAL mode: gadget driver present\n");
29662306a36Sopenharmony_ci			tahvo_usb_become_peripheral(tu);
29762306a36Sopenharmony_ci		} else {
29862306a36Sopenharmony_ci			dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n");
29962306a36Sopenharmony_ci			tahvo_usb_power_off(tu);
30062306a36Sopenharmony_ci		}
30162306a36Sopenharmony_ci		r = strlen(buf);
30262306a36Sopenharmony_ci	} else {
30362306a36Sopenharmony_ci		r = -EINVAL;
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci	mutex_unlock(&tu->serialize);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	return r;
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_cistatic DEVICE_ATTR_RW(otg_mode);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic struct attribute *tahvo_attrs[] = {
31262306a36Sopenharmony_ci	&dev_attr_vbus.attr,
31362306a36Sopenharmony_ci	&dev_attr_otg_mode.attr,
31462306a36Sopenharmony_ci	NULL
31562306a36Sopenharmony_ci};
31662306a36Sopenharmony_ciATTRIBUTE_GROUPS(tahvo);
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_cistatic int tahvo_usb_probe(struct platform_device *pdev)
31962306a36Sopenharmony_ci{
32062306a36Sopenharmony_ci	struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
32162306a36Sopenharmony_ci	struct tahvo_usb *tu;
32262306a36Sopenharmony_ci	int ret;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL);
32562306a36Sopenharmony_ci	if (!tu)
32662306a36Sopenharmony_ci		return -ENOMEM;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg),
32962306a36Sopenharmony_ci				   GFP_KERNEL);
33062306a36Sopenharmony_ci	if (!tu->phy.otg)
33162306a36Sopenharmony_ci		return -ENOMEM;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	tu->pt_dev = pdev;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	/* Default mode */
33662306a36Sopenharmony_ci#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT
33762306a36Sopenharmony_ci	tu->tahvo_mode = TAHVO_MODE_HOST;
33862306a36Sopenharmony_ci#else
33962306a36Sopenharmony_ci	tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
34062306a36Sopenharmony_ci#endif
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	mutex_init(&tu->serialize);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick");
34562306a36Sopenharmony_ci	if (!IS_ERR(tu->ick))
34662306a36Sopenharmony_ci		clk_enable(tu->ick);
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	/*
34962306a36Sopenharmony_ci	 * Set initial state, so that we generate kevents only on state changes.
35062306a36Sopenharmony_ci	 */
35162306a36Sopenharmony_ci	tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	tu->extcon = devm_extcon_dev_allocate(&pdev->dev, tahvo_cable);
35462306a36Sopenharmony_ci	if (IS_ERR(tu->extcon)) {
35562306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
35662306a36Sopenharmony_ci		ret = PTR_ERR(tu->extcon);
35762306a36Sopenharmony_ci		goto err_disable_clk;
35862306a36Sopenharmony_ci	}
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	ret = devm_extcon_dev_register(&pdev->dev, tu->extcon);
36162306a36Sopenharmony_ci	if (ret) {
36262306a36Sopenharmony_ci		dev_err(&pdev->dev, "could not register extcon device: %d\n",
36362306a36Sopenharmony_ci			ret);
36462306a36Sopenharmony_ci		goto err_disable_clk;
36562306a36Sopenharmony_ci	}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	/* Set the initial cable state. */
36862306a36Sopenharmony_ci	extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST,
36962306a36Sopenharmony_ci			       tu->tahvo_mode == TAHVO_MODE_HOST);
37062306a36Sopenharmony_ci	extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	/* Create OTG interface */
37362306a36Sopenharmony_ci	tahvo_usb_power_off(tu);
37462306a36Sopenharmony_ci	tu->phy.dev = &pdev->dev;
37562306a36Sopenharmony_ci	tu->phy.otg->state = OTG_STATE_UNDEFINED;
37662306a36Sopenharmony_ci	tu->phy.label = DRIVER_NAME;
37762306a36Sopenharmony_ci	tu->phy.set_suspend = tahvo_usb_set_suspend;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	tu->phy.otg->usb_phy = &tu->phy;
38062306a36Sopenharmony_ci	tu->phy.otg->set_host = tahvo_usb_set_host;
38162306a36Sopenharmony_ci	tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2);
38462306a36Sopenharmony_ci	if (ret < 0) {
38562306a36Sopenharmony_ci		dev_err(&pdev->dev, "cannot register USB transceiver: %d\n",
38662306a36Sopenharmony_ci			ret);
38762306a36Sopenharmony_ci		goto err_disable_clk;
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	dev_set_drvdata(&pdev->dev, tu);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	tu->irq = ret = platform_get_irq(pdev, 0);
39362306a36Sopenharmony_ci	if (ret < 0)
39462306a36Sopenharmony_ci		goto err_remove_phy;
39562306a36Sopenharmony_ci	ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt,
39662306a36Sopenharmony_ci				   IRQF_ONESHOT,
39762306a36Sopenharmony_ci				   "tahvo-vbus", tu);
39862306a36Sopenharmony_ci	if (ret) {
39962306a36Sopenharmony_ci		dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n",
40062306a36Sopenharmony_ci			ret);
40162306a36Sopenharmony_ci		goto err_remove_phy;
40262306a36Sopenharmony_ci	}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	return 0;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cierr_remove_phy:
40762306a36Sopenharmony_ci	usb_remove_phy(&tu->phy);
40862306a36Sopenharmony_cierr_disable_clk:
40962306a36Sopenharmony_ci	if (!IS_ERR(tu->ick))
41062306a36Sopenharmony_ci		clk_disable(tu->ick);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	return ret;
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic void tahvo_usb_remove(struct platform_device *pdev)
41662306a36Sopenharmony_ci{
41762306a36Sopenharmony_ci	struct tahvo_usb *tu = platform_get_drvdata(pdev);
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	free_irq(tu->irq, tu);
42062306a36Sopenharmony_ci	usb_remove_phy(&tu->phy);
42162306a36Sopenharmony_ci	if (!IS_ERR(tu->ick))
42262306a36Sopenharmony_ci		clk_disable(tu->ick);
42362306a36Sopenharmony_ci}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_cistatic struct platform_driver tahvo_usb_driver = {
42662306a36Sopenharmony_ci	.probe		= tahvo_usb_probe,
42762306a36Sopenharmony_ci	.remove_new	= tahvo_usb_remove,
42862306a36Sopenharmony_ci	.driver		= {
42962306a36Sopenharmony_ci		.name	= "tahvo-usb",
43062306a36Sopenharmony_ci		.dev_groups = tahvo_groups,
43162306a36Sopenharmony_ci	},
43262306a36Sopenharmony_ci};
43362306a36Sopenharmony_cimodule_platform_driver(tahvo_usb_driver);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ciMODULE_DESCRIPTION("Tahvo USB transceiver driver");
43662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
43762306a36Sopenharmony_ciMODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs");
43862306a36Sopenharmony_ciMODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
439