mtu3_dr.c 10.6 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
C
Chunfeng Yun 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
/*
 * mtu3_dr.c - dual role switch and host glue layer
 *
 * Copyright (C) 2016 MediaTek Inc.
 *
 * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
 */

#include <linux/debugfs.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/of_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>

#include "mtu3.h"
#include "mtu3_dr.h"

#define USB2_PORT 2
#define USB3_PORT 3

enum mtu3_vbus_id_state {
	MTU3_ID_FLOAT = 1,
	MTU3_ID_GROUND,
	MTU3_VBUS_OFF,
	MTU3_VBUS_VALID,
};

static void toggle_opstate(struct ssusb_mtk *ssusb)
{
	if (!ssusb->otg_switch.is_u3_drd) {
		mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION);
		mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN);
	}
}

/* only port0 supports dual-role mode */
static int ssusb_port0_switch(struct ssusb_mtk *ssusb,
	int version, bool tohost)
{
	void __iomem *ibase = ssusb->ippc_base;
	u32 value;

	dev_dbg(ssusb->dev, "%s (switch u%d port0 to %s)\n", __func__,
		version, tohost ? "host" : "device");

	if (version == USB2_PORT) {
		/* 1. power off and disable u2 port0 */
		value = mtu3_readl(ibase, SSUSB_U2_CTRL(0));
		value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS;
		mtu3_writel(ibase, SSUSB_U2_CTRL(0), value);

		/* 2. power on, enable u2 port0 and select its mode */
		value = mtu3_readl(ibase, SSUSB_U2_CTRL(0));
		value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS);
		value = tohost ? (value | SSUSB_U2_PORT_HOST_SEL) :
			(value & (~SSUSB_U2_PORT_HOST_SEL));
		mtu3_writel(ibase, SSUSB_U2_CTRL(0), value);
	} else {
		/* 1. power off and disable u3 port0 */
		value = mtu3_readl(ibase, SSUSB_U3_CTRL(0));
		value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS;
		mtu3_writel(ibase, SSUSB_U3_CTRL(0), value);

		/* 2. power on, enable u3 port0 and select its mode */
		value = mtu3_readl(ibase, SSUSB_U3_CTRL(0));
		value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS);
		value = tohost ? (value | SSUSB_U3_PORT_HOST_SEL) :
			(value & (~SSUSB_U3_PORT_HOST_SEL));
		mtu3_writel(ibase, SSUSB_U3_CTRL(0), value);
	}

	return 0;
}

static void switch_port_to_host(struct ssusb_mtk *ssusb)
{
	u32 check_clk = 0;

	dev_dbg(ssusb->dev, "%s\n", __func__);

	ssusb_port0_switch(ssusb, USB2_PORT, true);

	if (ssusb->otg_switch.is_u3_drd) {
		ssusb_port0_switch(ssusb, USB3_PORT, true);
		check_clk = SSUSB_U3_MAC_RST_B_STS;
	}

	ssusb_check_clocks(ssusb, check_clk);

	/* after all clocks are stable */
	toggle_opstate(ssusb);
}

static void switch_port_to_device(struct ssusb_mtk *ssusb)
{
	u32 check_clk = 0;

	dev_dbg(ssusb->dev, "%s\n", __func__);

	ssusb_port0_switch(ssusb, USB2_PORT, false);

	if (ssusb->otg_switch.is_u3_drd) {
		ssusb_port0_switch(ssusb, USB3_PORT, false);
		check_clk = SSUSB_U3_MAC_RST_B_STS;
	}

	ssusb_check_clocks(ssusb, check_clk);
}

int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on)
{
	struct ssusb_mtk *ssusb =
		container_of(otg_sx, struct ssusb_mtk, otg_switch);
	struct regulator *vbus = otg_sx->vbus;
	int ret;

	/* vbus is optional */
	if (!vbus)
		return 0;

	dev_dbg(ssusb->dev, "%s: turn %s\n", __func__, is_on ? "on" : "off");

	if (is_on) {
		ret = regulator_enable(vbus);
		if (ret) {
			dev_err(ssusb->dev, "vbus regulator enable failed\n");
			return ret;
		}
	} else {
		regulator_disable(vbus);
	}

	return 0;
}

/*
 * switch to host: -> MTU3_VBUS_OFF --> MTU3_ID_GROUND
 * switch to device: -> MTU3_ID_FLOAT --> MTU3_VBUS_VALID
 */
static void ssusb_set_mailbox(struct otg_switch_mtk *otg_sx,
	enum mtu3_vbus_id_state status)
{
	struct ssusb_mtk *ssusb =
		container_of(otg_sx, struct ssusb_mtk, otg_switch);
	struct mtu3 *mtu = ssusb->u3d;

	dev_dbg(ssusb->dev, "mailbox state(%d)\n", status);

	switch (status) {
	case MTU3_ID_GROUND:
		switch_port_to_host(ssusb);
		ssusb_set_vbus(otg_sx, 1);
		ssusb->is_host = true;
		break;
	case MTU3_ID_FLOAT:
		ssusb->is_host = false;
		ssusb_set_vbus(otg_sx, 0);
		switch_port_to_device(ssusb);
		break;
	case MTU3_VBUS_OFF:
		mtu3_stop(mtu);
		pm_relax(ssusb->dev);
		break;
	case MTU3_VBUS_VALID:
		/* avoid suspend when works as device */
		pm_stay_awake(ssusb->dev);
		mtu3_start(mtu);
		break;
	default:
		dev_err(ssusb->dev, "invalid state\n");
	}
}

177
static void ssusb_id_work(struct work_struct *work)
C
Chunfeng Yun 已提交
178 179
{
	struct otg_switch_mtk *otg_sx =
180
		container_of(work, struct otg_switch_mtk, id_work);
C
Chunfeng Yun 已提交
181

182
	if (otg_sx->id_event)
C
Chunfeng Yun 已提交
183 184 185
		ssusb_set_mailbox(otg_sx, MTU3_ID_GROUND);
	else
		ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT);
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
}

static void ssusb_vbus_work(struct work_struct *work)
{
	struct otg_switch_mtk *otg_sx =
		container_of(work, struct otg_switch_mtk, vbus_work);

	if (otg_sx->vbus_event)
		ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID);
	else
		ssusb_set_mailbox(otg_sx, MTU3_VBUS_OFF);
}

/*
 * @ssusb_id_notifier is called in atomic context, but @ssusb_set_mailbox
 * may sleep, so use work queue here
 */
static int ssusb_id_notifier(struct notifier_block *nb,
	unsigned long event, void *ptr)
{
	struct otg_switch_mtk *otg_sx =
		container_of(nb, struct otg_switch_mtk, id_nb);

	otg_sx->id_event = event;
	schedule_work(&otg_sx->id_work);
C
Chunfeng Yun 已提交
211 212 213 214 215 216 217 218 219 220

	return NOTIFY_DONE;
}

static int ssusb_vbus_notifier(struct notifier_block *nb,
	unsigned long event, void *ptr)
{
	struct otg_switch_mtk *otg_sx =
		container_of(nb, struct otg_switch_mtk, vbus_nb);

221 222
	otg_sx->vbus_event = event;
	schedule_work(&otg_sx->vbus_work);
C
Chunfeng Yun 已提交
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238

	return NOTIFY_DONE;
}

static int ssusb_extcon_register(struct otg_switch_mtk *otg_sx)
{
	struct ssusb_mtk *ssusb =
		container_of(otg_sx, struct ssusb_mtk, otg_switch);
	struct extcon_dev *edev = otg_sx->edev;
	int ret;

	/* extcon is optional */
	if (!edev)
		return 0;

	otg_sx->vbus_nb.notifier_call = ssusb_vbus_notifier;
C
Chanwoo Choi 已提交
239
	ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB,
C
Chunfeng Yun 已提交
240
					&otg_sx->vbus_nb);
241
	if (ret < 0) {
C
Chunfeng Yun 已提交
242
		dev_err(ssusb->dev, "failed to register notifier for USB\n");
243 244
		return ret;
	}
C
Chunfeng Yun 已提交
245 246

	otg_sx->id_nb.notifier_call = ssusb_id_notifier;
C
Chanwoo Choi 已提交
247
	ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST,
C
Chunfeng Yun 已提交
248
					&otg_sx->id_nb);
249
	if (ret < 0) {
C
Chunfeng Yun 已提交
250
		dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n");
251 252
		return ret;
	}
C
Chunfeng Yun 已提交
253 254

	dev_dbg(ssusb->dev, "EXTCON_USB: %d, EXTCON_USB_HOST: %d\n",
C
Chanwoo Choi 已提交
255 256
		extcon_get_state(edev, EXTCON_USB),
		extcon_get_state(edev, EXTCON_USB_HOST));
C
Chunfeng Yun 已提交
257 258

	/* default as host, switch to device mode if needed */
C
Chanwoo Choi 已提交
259
	if (extcon_get_state(edev, EXTCON_USB_HOST) == false)
C
Chunfeng Yun 已提交
260
		ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT);
C
Chanwoo Choi 已提交
261
	if (extcon_get_state(edev, EXTCON_USB) == true)
C
Chunfeng Yun 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
		ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID);

	return 0;
}

/*
 * We provide an interface via debugfs to switch between host and device modes
 * depending on user input.
 * This is useful in special cases, such as uses TYPE-A receptacle but also
 * wants to support dual-role mode.
 */
static void ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host)
{
	struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;

277 278 279 280 281 282 283 284 285
	if (to_host) {
		ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST);
		ssusb_set_mailbox(otg_sx, MTU3_VBUS_OFF);
		ssusb_set_mailbox(otg_sx, MTU3_ID_GROUND);
	} else {
		ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_DEVICE);
		ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT);
		ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID);
	}
C
Chunfeng Yun 已提交
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
}

static int ssusb_mode_show(struct seq_file *sf, void *unused)
{
	struct ssusb_mtk *ssusb = sf->private;

	seq_printf(sf, "current mode: %s(%s drd)\n(echo device/host)\n",
		ssusb->is_host ? "host" : "device",
		ssusb->otg_switch.manual_drd_enabled ? "manual" : "auto");

	return 0;
}

static int ssusb_mode_open(struct inode *inode, struct file *file)
{
	return single_open(file, ssusb_mode_show, inode->i_private);
}

static ssize_t ssusb_mode_write(struct file *file,
	const char __user *ubuf, size_t count, loff_t *ppos)
{
	struct seq_file *sf = file->private_data;
	struct ssusb_mtk *ssusb = sf->private;
	char buf[16];

	if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
		return -EFAULT;

	if (!strncmp(buf, "host", 4) && !ssusb->is_host) {
		ssusb_mode_manual_switch(ssusb, 1);
	} else if (!strncmp(buf, "device", 6) && ssusb->is_host) {
		ssusb_mode_manual_switch(ssusb, 0);
	} else {
		dev_err(ssusb->dev, "wrong or duplicated setting\n");
		return -EINVAL;
	}

	return count;
}

static const struct file_operations ssusb_mode_fops = {
	.open = ssusb_mode_open,
	.write = ssusb_mode_write,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
static int ssusb_vbus_show(struct seq_file *sf, void *unused)
{
	struct ssusb_mtk *ssusb = sf->private;
	struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;

	seq_printf(sf, "vbus state: %s\n(echo on/off)\n",
		regulator_is_enabled(otg_sx->vbus) ? "on" : "off");

	return 0;
}

static int ssusb_vbus_open(struct inode *inode, struct file *file)
{
	return single_open(file, ssusb_vbus_show, inode->i_private);
}

static ssize_t ssusb_vbus_write(struct file *file,
	const char __user *ubuf, size_t count, loff_t *ppos)
{
	struct seq_file *sf = file->private_data;
	struct ssusb_mtk *ssusb = sf->private;
	struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
	char buf[16];
	bool enable;

	if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
		return -EFAULT;

	if (kstrtobool(buf, &enable)) {
		dev_err(ssusb->dev, "wrong setting\n");
		return -EINVAL;
	}

	ssusb_set_vbus(otg_sx, enable);

	return count;
}

static const struct file_operations ssusb_vbus_fops = {
	.open = ssusb_vbus_open,
	.write = ssusb_vbus_write,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

C
Chunfeng Yun 已提交
380 381
static void ssusb_debugfs_init(struct ssusb_mtk *ssusb)
{
382
	struct dentry *root = ssusb->dbgfs_root;
C
Chunfeng Yun 已提交
383

384 385
	debugfs_create_file("mode", 0644, root, ssusb, &ssusb_mode_fops);
	debugfs_create_file("vbus", 0644, root, ssusb, &ssusb_vbus_fops);
C
Chunfeng Yun 已提交
386 387
}

388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
void ssusb_set_force_mode(struct ssusb_mtk *ssusb,
			  enum mtu3_dr_force_mode mode)
{
	u32 value;

	value = mtu3_readl(ssusb->ippc_base, SSUSB_U2_CTRL(0));
	switch (mode) {
	case MTU3_DR_FORCE_DEVICE:
		value |= SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG;
		break;
	case MTU3_DR_FORCE_HOST:
		value |= SSUSB_U2_PORT_FORCE_IDDIG;
		value &= ~SSUSB_U2_PORT_RG_IDDIG;
		break;
	case MTU3_DR_FORCE_NONE:
		value &= ~(SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG);
		break;
	default:
		return;
	}
	mtu3_writel(ssusb->ippc_base, SSUSB_U2_CTRL(0), value);
}

C
Chunfeng Yun 已提交
411 412 413
int ssusb_otg_switch_init(struct ssusb_mtk *ssusb)
{
	struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
414
	int ret = 0;
C
Chunfeng Yun 已提交
415

416 417 418
	INIT_WORK(&otg_sx->id_work, ssusb_id_work);
	INIT_WORK(&otg_sx->vbus_work, ssusb_vbus_work);

419
	if (otg_sx->manual_drd_enabled)
C
Chunfeng Yun 已提交
420
		ssusb_debugfs_init(ssusb);
421
	else
422
		ret = ssusb_extcon_register(otg_sx);
C
Chunfeng Yun 已提交
423

424
	return ret;
C
Chunfeng Yun 已提交
425 426 427 428 429 430
}

void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb)
{
	struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;

431 432
	cancel_work_sync(&otg_sx->id_work);
	cancel_work_sync(&otg_sx->vbus_work);
C
Chunfeng Yun 已提交
433
}