mtu3_plat.c 10.7 KB
Newer Older
C
Chunfeng Yun 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * Copyright (C) 2016 MediaTek Inc.
 *
 * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
C
Chunfeng Yun 已提交
24
#include <linux/pinctrl/consumer.h>
C
Chunfeng Yun 已提交
25 26 27
#include <linux/platform_device.h>

#include "mtu3.h"
C
Chunfeng Yun 已提交
28
#include "mtu3_dr.h"
C
Chunfeng Yun 已提交
29 30

/* u2-port0 should be powered on and enabled; */
C
Chunfeng Yun 已提交
31
int ssusb_check_clocks(struct ssusb_mtk *ssusb, u32 ex_clks)
C
Chunfeng Yun 已提交
32
{
C
Chunfeng Yun 已提交
33
	void __iomem *ibase = ssusb->ippc_base;
C
Chunfeng Yun 已提交
34 35 36 37 38 39 40 41 42
	u32 value, check_val;
	int ret;

	check_val = ex_clks | SSUSB_SYS125_RST_B_STS | SSUSB_SYSPLL_STABLE |
			SSUSB_REF_RST_B_STS;

	ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value,
			(check_val == (value & check_val)), 100, 20000);
	if (ret) {
C
Chunfeng Yun 已提交
43
		dev_err(ssusb->dev, "clks of sts1 are not stable!\n");
C
Chunfeng Yun 已提交
44 45 46 47 48 49
		return ret;
	}

	ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS2, value,
			(value & SSUSB_U2_MAC_SYS_RST_B_STS), 100, 10000);
	if (ret) {
C
Chunfeng Yun 已提交
50
		dev_err(ssusb->dev, "mac2 clock is not stable\n");
C
Chunfeng Yun 已提交
51 52 53 54 55 56
		return ret;
	}

	return 0;
}

C
Chunfeng Yun 已提交
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
static int ssusb_phy_init(struct ssusb_mtk *ssusb)
{
	int i;
	int ret;

	for (i = 0; i < ssusb->num_phys; i++) {
		ret = phy_init(ssusb->phys[i]);
		if (ret)
			goto exit_phy;
	}
	return 0;

exit_phy:
	for (; i > 0; i--)
		phy_exit(ssusb->phys[i - 1]);

	return ret;
}

static int ssusb_phy_exit(struct ssusb_mtk *ssusb)
{
	int i;

	for (i = 0; i < ssusb->num_phys; i++)
		phy_exit(ssusb->phys[i]);

	return 0;
}

static int ssusb_phy_power_on(struct ssusb_mtk *ssusb)
{
	int i;
	int ret;

	for (i = 0; i < ssusb->num_phys; i++) {
		ret = phy_power_on(ssusb->phys[i]);
		if (ret)
			goto power_off_phy;
	}
	return 0;

power_off_phy:
	for (; i > 0; i--)
		phy_power_off(ssusb->phys[i - 1]);

	return ret;
}

static void ssusb_phy_power_off(struct ssusb_mtk *ssusb)
{
	unsigned int i;

	for (i = 0; i < ssusb->num_phys; i++)
		phy_power_off(ssusb->phys[i]);
}

static int ssusb_rscs_init(struct ssusb_mtk *ssusb)
C
Chunfeng Yun 已提交
114 115 116
{
	int ret = 0;

C
Chunfeng Yun 已提交
117
	ret = regulator_enable(ssusb->vusb33);
C
Chunfeng Yun 已提交
118
	if (ret) {
C
Chunfeng Yun 已提交
119
		dev_err(ssusb->dev, "failed to enable vusb33\n");
C
Chunfeng Yun 已提交
120 121 122
		goto vusb33_err;
	}

C
Chunfeng Yun 已提交
123
	ret = clk_prepare_enable(ssusb->sys_clk);
C
Chunfeng Yun 已提交
124
	if (ret) {
C
Chunfeng Yun 已提交
125
		dev_err(ssusb->dev, "failed to enable sys_clk\n");
C
Chunfeng Yun 已提交
126 127 128
		goto clk_err;
	}

C
Chunfeng Yun 已提交
129
	ret = ssusb_phy_init(ssusb);
C
Chunfeng Yun 已提交
130
	if (ret) {
C
Chunfeng Yun 已提交
131
		dev_err(ssusb->dev, "failed to init phy\n");
C
Chunfeng Yun 已提交
132 133 134
		goto phy_init_err;
	}

C
Chunfeng Yun 已提交
135
	ret = ssusb_phy_power_on(ssusb);
C
Chunfeng Yun 已提交
136
	if (ret) {
C
Chunfeng Yun 已提交
137
		dev_err(ssusb->dev, "failed to power on phy\n");
C
Chunfeng Yun 已提交
138 139 140 141 142 143
		goto phy_err;
	}

	return 0;

phy_err:
C
Chunfeng Yun 已提交
144
	ssusb_phy_exit(ssusb);
C
Chunfeng Yun 已提交
145
phy_init_err:
C
Chunfeng Yun 已提交
146
	clk_disable_unprepare(ssusb->sys_clk);
C
Chunfeng Yun 已提交
147
clk_err:
C
Chunfeng Yun 已提交
148
	regulator_disable(ssusb->vusb33);
C
Chunfeng Yun 已提交
149 150 151 152 153
vusb33_err:

	return ret;
}

C
Chunfeng Yun 已提交
154
static void ssusb_rscs_exit(struct ssusb_mtk *ssusb)
C
Chunfeng Yun 已提交
155
{
C
Chunfeng Yun 已提交
156 157 158 159
	clk_disable_unprepare(ssusb->sys_clk);
	regulator_disable(ssusb->vusb33);
	ssusb_phy_power_off(ssusb);
	ssusb_phy_exit(ssusb);
C
Chunfeng Yun 已提交
160 161
}

C
Chunfeng Yun 已提交
162
static void ssusb_ip_sw_reset(struct ssusb_mtk *ssusb)
C
Chunfeng Yun 已提交
163
{
C
Chunfeng Yun 已提交
164 165
	/* reset whole ip (xhci & u3d) */
	mtu3_setbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL0, SSUSB_IP_SW_RST);
C
Chunfeng Yun 已提交
166
	udelay(1);
C
Chunfeng Yun 已提交
167
	mtu3_clrbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL0, SSUSB_IP_SW_RST);
C
Chunfeng Yun 已提交
168 169
}

C
Chunfeng Yun 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
static int get_iddig_pinctrl(struct ssusb_mtk *ssusb)
{
	struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;

	otg_sx->id_pinctrl = devm_pinctrl_get(ssusb->dev);
	if (IS_ERR(otg_sx->id_pinctrl)) {
		dev_err(ssusb->dev, "Cannot find id pinctrl!\n");
		return PTR_ERR(otg_sx->id_pinctrl);
	}

	otg_sx->id_float =
		pinctrl_lookup_state(otg_sx->id_pinctrl, "id_float");
	if (IS_ERR(otg_sx->id_float)) {
		dev_err(ssusb->dev, "Cannot find pinctrl id_float!\n");
		return PTR_ERR(otg_sx->id_float);
	}

	otg_sx->id_ground =
		pinctrl_lookup_state(otg_sx->id_pinctrl, "id_ground");
	if (IS_ERR(otg_sx->id_ground)) {
		dev_err(ssusb->dev, "Cannot find pinctrl id_ground!\n");
		return PTR_ERR(otg_sx->id_ground);
	}

	return 0;
}

C
Chunfeng Yun 已提交
197
static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
C
Chunfeng Yun 已提交
198 199
{
	struct device_node *node = pdev->dev.of_node;
C
Chunfeng Yun 已提交
200
	struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
C
Chunfeng Yun 已提交
201
	struct device *dev = &pdev->dev;
C
Chunfeng Yun 已提交
202
	struct regulator *vbus;
C
Chunfeng Yun 已提交
203
	struct resource *res;
C
Chunfeng Yun 已提交
204 205
	int i;
	int ret;
C
Chunfeng Yun 已提交
206

C
Chunfeng Yun 已提交
207 208 209 210 211 212 213 214 215
	ssusb->num_phys = of_count_phandle_with_args(node,
			"phys", "#phy-cells");
	if (ssusb->num_phys > 0) {
		ssusb->phys = devm_kcalloc(dev, ssusb->num_phys,
					sizeof(*ssusb->phys), GFP_KERNEL);
		if (!ssusb->phys)
			return -ENOMEM;
	} else {
		ssusb->num_phys = 0;
C
Chunfeng Yun 已提交
216 217
	}

C
Chunfeng Yun 已提交
218 219 220 221 222 223
	for (i = 0; i < ssusb->num_phys; i++) {
		ssusb->phys[i] = devm_of_phy_get_by_index(dev, node, i);
		if (IS_ERR(ssusb->phys[i])) {
			dev_err(dev, "failed to get phy-%d\n", i);
			return PTR_ERR(ssusb->phys[i]);
		}
C
Chunfeng Yun 已提交
224 225 226
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ippc");
C
Chunfeng Yun 已提交
227 228
	ssusb->ippc_base = devm_ioremap_resource(dev, res);
	if (IS_ERR(ssusb->ippc_base)) {
C
Chunfeng Yun 已提交
229
		dev_err(dev, "failed to map memory for ippc\n");
C
Chunfeng Yun 已提交
230
		return PTR_ERR(ssusb->ippc_base);
C
Chunfeng Yun 已提交
231 232
	}

C
Chunfeng Yun 已提交
233 234
	ssusb->vusb33 = devm_regulator_get(&pdev->dev, "vusb33");
	if (IS_ERR(ssusb->vusb33)) {
C
Chunfeng Yun 已提交
235
		dev_err(dev, "failed to get vusb33\n");
C
Chunfeng Yun 已提交
236
		return PTR_ERR(ssusb->vusb33);
C
Chunfeng Yun 已提交
237 238
	}

C
Chunfeng Yun 已提交
239 240
	ssusb->sys_clk = devm_clk_get(dev, "sys_ck");
	if (IS_ERR(ssusb->sys_clk)) {
C
Chunfeng Yun 已提交
241
		dev_err(dev, "failed to get sys clock\n");
C
Chunfeng Yun 已提交
242 243 244 245 246 247 248
		return PTR_ERR(ssusb->sys_clk);
	}

	ssusb->dr_mode = usb_get_dr_mode(dev);
	if (ssusb->dr_mode == USB_DR_MODE_UNKNOWN) {
		dev_err(dev, "dr_mode is error\n");
		return -EINVAL;
C
Chunfeng Yun 已提交
249 250
	}

C
Chunfeng Yun 已提交
251 252 253 254 255 256 257 258
	if (ssusb->dr_mode == USB_DR_MODE_PERIPHERAL)
		return 0;

	/* if host role is supported */
	ret = ssusb_wakeup_of_property_parse(ssusb, node);
	if (ret)
		return ret;

C
Chunfeng Yun 已提交
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
	if (ssusb->dr_mode != USB_DR_MODE_OTG)
		return 0;

	/* if dual-role mode is supported */
	vbus = devm_regulator_get(&pdev->dev, "vbus");
	if (IS_ERR(vbus)) {
		dev_err(dev, "failed to get vbus\n");
		return PTR_ERR(vbus);
	}
	otg_sx->vbus = vbus;

	otg_sx->is_u3_drd = of_property_read_bool(node, "mediatek,usb3-drd");
	otg_sx->manual_drd_enabled =
		of_property_read_bool(node, "enable-manual-drd");

	if (of_property_read_bool(node, "extcon")) {
		otg_sx->edev = extcon_get_edev_by_phandle(ssusb->dev, 0);
		if (IS_ERR(otg_sx->edev)) {
			dev_err(ssusb->dev, "couldn't get extcon device\n");
			return -EPROBE_DEFER;
		}
		if (otg_sx->manual_drd_enabled) {
			ret = get_iddig_pinctrl(ssusb);
			if (ret)
				return ret;
		}
	}

	dev_info(dev, "dr_mode: %d, is_u3_dr: %d\n",
		ssusb->dr_mode, otg_sx->is_u3_drd);

C
Chunfeng Yun 已提交
290 291 292 293 294
	return 0;
}

static int mtu3_probe(struct platform_device *pdev)
{
C
Chunfeng Yun 已提交
295
	struct device_node *node = pdev->dev.of_node;
C
Chunfeng Yun 已提交
296
	struct device *dev = &pdev->dev;
C
Chunfeng Yun 已提交
297
	struct ssusb_mtk *ssusb;
C
Chunfeng Yun 已提交
298 299 300
	int ret = -ENOMEM;

	/* all elements are set to ZERO as default value */
C
Chunfeng Yun 已提交
301 302
	ssusb = devm_kzalloc(dev, sizeof(*ssusb), GFP_KERNEL);
	if (!ssusb)
C
Chunfeng Yun 已提交
303 304 305 306 307 308 309 310
		return -ENOMEM;

	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
	if (ret) {
		dev_err(dev, "No suitable DMA config available\n");
		return -ENOTSUPP;
	}

C
Chunfeng Yun 已提交
311 312
	platform_set_drvdata(pdev, ssusb);
	ssusb->dev = dev;
C
Chunfeng Yun 已提交
313

C
Chunfeng Yun 已提交
314
	ret = get_ssusb_rscs(pdev, ssusb);
C
Chunfeng Yun 已提交
315 316 317 318 319 320 321 322
	if (ret)
		return ret;

	/* enable power domain */
	pm_runtime_enable(dev);
	pm_runtime_get_sync(dev);
	device_enable_async_suspend(dev);

C
Chunfeng Yun 已提交
323
	ret = ssusb_rscs_init(ssusb);
C
Chunfeng Yun 已提交
324 325 326
	if (ret)
		goto comm_init_err;

C
Chunfeng Yun 已提交
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
	ssusb_ip_sw_reset(ssusb);

	if (IS_ENABLED(CONFIG_USB_MTU3_HOST))
		ssusb->dr_mode = USB_DR_MODE_HOST;
	else if (IS_ENABLED(CONFIG_USB_MTU3_GADGET))
		ssusb->dr_mode = USB_DR_MODE_PERIPHERAL;

	/* default as host */
	ssusb->is_host = !(ssusb->dr_mode == USB_DR_MODE_PERIPHERAL);

	switch (ssusb->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
		ret = ssusb_gadget_init(ssusb);
		if (ret) {
			dev_err(dev, "failed to initialize gadget\n");
			goto comm_exit;
		}
		break;
	case USB_DR_MODE_HOST:
		ret = ssusb_host_init(ssusb, node);
		if (ret) {
			dev_err(dev, "failed to initialize host\n");
			goto comm_exit;
		}
		break;
C
Chunfeng Yun 已提交
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
	case USB_DR_MODE_OTG:
		ret = ssusb_gadget_init(ssusb);
		if (ret) {
			dev_err(dev, "failed to initialize gadget\n");
			goto comm_exit;
		}

		ret = ssusb_host_init(ssusb, node);
		if (ret) {
			dev_err(dev, "failed to initialize host\n");
			goto gadget_exit;
		}

		ssusb_otg_switch_init(ssusb);
		break;
C
Chunfeng Yun 已提交
367 368 369
	default:
		dev_err(dev, "unsupported mode: %d\n", ssusb->dr_mode);
		ret = -EINVAL;
C
Chunfeng Yun 已提交
370 371 372 373 374
		goto comm_exit;
	}

	return 0;

C
Chunfeng Yun 已提交
375 376
gadget_exit:
	ssusb_gadget_exit(ssusb);
C
Chunfeng Yun 已提交
377
comm_exit:
C
Chunfeng Yun 已提交
378
	ssusb_rscs_exit(ssusb);
C
Chunfeng Yun 已提交
379 380 381 382 383 384 385 386 387
comm_init_err:
	pm_runtime_put_sync(dev);
	pm_runtime_disable(dev);

	return ret;
}

static int mtu3_remove(struct platform_device *pdev)
{
C
Chunfeng Yun 已提交
388 389 390 391 392 393 394 395 396
	struct ssusb_mtk *ssusb = platform_get_drvdata(pdev);

	switch (ssusb->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
		ssusb_gadget_exit(ssusb);
		break;
	case USB_DR_MODE_HOST:
		ssusb_host_exit(ssusb);
		break;
C
Chunfeng Yun 已提交
397 398 399 400 401
	case USB_DR_MODE_OTG:
		ssusb_otg_switch_exit(ssusb);
		ssusb_gadget_exit(ssusb);
		ssusb_host_exit(ssusb);
		break;
C
Chunfeng Yun 已提交
402 403 404
	default:
		return -EINVAL;
	}
C
Chunfeng Yun 已提交
405

C
Chunfeng Yun 已提交
406
	ssusb_rscs_exit(ssusb);
C
Chunfeng Yun 已提交
407 408 409 410 411 412
	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_disable(&pdev->dev);

	return 0;
}

C
Chunfeng Yun 已提交
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
/*
 * when support dual-role mode, we reject suspend when
 * it works as device mode;
 */
static int __maybe_unused mtu3_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ssusb_mtk *ssusb = platform_get_drvdata(pdev);

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

	/* REVISIT: disconnect it for only device mode? */
	if (!ssusb->is_host)
		return 0;

	ssusb_host_disable(ssusb, true);
	ssusb_phy_power_off(ssusb);
	clk_disable_unprepare(ssusb->sys_clk);
	ssusb_wakeup_enable(ssusb);

	return 0;
}

static int __maybe_unused mtu3_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ssusb_mtk *ssusb = platform_get_drvdata(pdev);

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

	if (!ssusb->is_host)
		return 0;

	ssusb_wakeup_disable(ssusb);
	clk_prepare_enable(ssusb->sys_clk);
	ssusb_phy_power_on(ssusb);
	ssusb_host_enable(ssusb);

	return 0;
}

static const struct dev_pm_ops mtu3_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(mtu3_suspend, mtu3_resume)
};

#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &mtu3_pm_ops : NULL)

C
Chunfeng Yun 已提交
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
#ifdef CONFIG_OF

static const struct of_device_id mtu3_of_match[] = {
	{.compatible = "mediatek,mt8173-mtu3",},
	{},
};

MODULE_DEVICE_TABLE(of, mtu3_of_match);

#endif

static struct platform_driver mtu3_driver = {
	.probe = mtu3_probe,
	.remove = mtu3_remove,
	.driver = {
		.name = MTU3_DRIVER_NAME,
C
Chunfeng Yun 已提交
476
		.pm = DEV_PM_OPS,
C
Chunfeng Yun 已提交
477 478 479 480 481 482 483 484
		.of_match_table = of_match_ptr(mtu3_of_match),
	},
};
module_platform_driver(mtu3_driver);

MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MediaTek USB3 DRD Controller Driver");