phy-omap-usb2.c 8.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * omap-usb2.c - USB PHY, talking to musb controller in OMAP.
 *
 * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Author: Kishon Vijay Abraham I <kishon@ti.com>
 *
 * 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/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/io.h>
24
#include <linux/phy/omap_usb.h>
25 26 27 28 29
#include <linux/usb/phy_companion.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
30
#include <linux/phy/omap_control_phy.h>
31
#include <linux/phy/phy.h>
32
#include <linux/of_platform.h>
33

34 35 36
#define USB2PHY_DISCON_BYP_LATCH (1 << 31)
#define USB2PHY_ANA_CONFIG1 0x4c

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
/**
 * omap_usb2_set_comparator - links the comparator present in the sytem with
 *	this phy
 * @comparator - the companion phy(comparator) for this phy
 *
 * The phy companion driver should call this API passing the phy_companion
 * filled with set_vbus and start_srp to be used by usb phy.
 *
 * For use by phy companion driver
 */
int omap_usb2_set_comparator(struct phy_companion *comparator)
{
	struct omap_usb	*phy;
	struct usb_phy	*x = usb_get_phy(USB_PHY_TYPE_USB2);

	if (IS_ERR(x))
		return -ENODEV;

	phy = phy_to_omapusb(x);
	phy->comparator = comparator;
	return 0;
}
EXPORT_SYMBOL_GPL(omap_usb2_set_comparator);

static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled)
{
	struct omap_usb *phy = phy_to_omapusb(otg->phy);

	if (!phy->comparator)
		return -ENODEV;

	return phy->comparator->set_vbus(phy->comparator, enabled);
}

static int omap_usb_start_srp(struct usb_otg *otg)
{
	struct omap_usb *phy = phy_to_omapusb(otg->phy);

	if (!phy->comparator)
		return -ENODEV;

	return phy->comparator->start_srp(phy->comparator);
}

static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
{
	struct usb_phy	*phy = otg->phy;

	otg->host = host;
	if (!host)
		phy->state = OTG_STATE_UNDEFINED;

	return 0;
}

static int omap_usb_set_peripheral(struct usb_otg *otg,
		struct usb_gadget *gadget)
{
	struct usb_phy	*phy = otg->phy;

	otg->gadget = gadget;
	if (!gadget)
		phy->state = OTG_STATE_UNDEFINED;

	return 0;
}

104 105 106 107
static int omap_usb_power_off(struct phy *x)
{
	struct omap_usb *phy = phy_get_drvdata(x);

108
	omap_control_phy_power(phy->control_dev, 0);
109 110 111 112 113 114 115 116

	return 0;
}

static int omap_usb_power_on(struct phy *x)
{
	struct omap_usb *phy = phy_get_drvdata(x);

117
	omap_control_phy_power(phy->control_dev, 1);
118 119 120 121

	return 0;
}

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
static int omap_usb_init(struct phy *x)
{
	struct omap_usb *phy = phy_get_drvdata(x);
	u32 val;

	if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
		/*
		 *
		 * Reduce the sensitivity of internal PHY by enabling the
		 * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This
		 * resolves issues with certain devices which can otherwise
		 * be prone to false disconnects.
		 *
		 */
		val = omap_usb_readl(phy->phy_base, USB2PHY_ANA_CONFIG1);
		val |= USB2PHY_DISCON_BYP_LATCH;
		omap_usb_writel(phy->phy_base, USB2PHY_ANA_CONFIG1, val);
	}

	return 0;
}

144
static struct phy_ops ops = {
145
	.init		= omap_usb_init,
146 147 148 149 150
	.power_on	= omap_usb_power_on,
	.power_off	= omap_usb_power_off,
	.owner		= THIS_MODULE,
};

151 152 153 154 155 156
#ifdef CONFIG_OF
static const struct usb_phy_data omap_usb2_data = {
	.label = "omap_usb2",
	.flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS,
};

157 158 159 160 161
static const struct usb_phy_data omap5_usb2_data = {
	.label = "omap5_usb2",
	.flags = 0,
};

162 163 164 165 166
static const struct usb_phy_data dra7x_usb2_data = {
	.label = "dra7x_usb2",
	.flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
};

167 168 169 170 171 172 173 174 175 176
static const struct usb_phy_data am437x_usb2_data = {
	.label = "am437x_usb2",
	.flags =  0,
};

static const struct of_device_id omap_usb2_id_table[] = {
	{
		.compatible = "ti,omap-usb2",
		.data = &omap_usb2_data,
	},
177 178 179 180
	{
		.compatible = "ti,omap5-usb2",
		.data = &omap5_usb2_data,
	},
181 182 183 184
	{
		.compatible = "ti,dra7x-usb2",
		.data = &dra7x_usb2_data,
	},
185 186 187 188 189 190 191 192 193
	{
		.compatible = "ti,am437x-usb2",
		.data = &am437x_usb2_data,
	},
	{},
};
MODULE_DEVICE_TABLE(of, omap_usb2_id_table);
#endif

B
Bill Pemberton 已提交
194
static int omap_usb2_probe(struct platform_device *pdev)
195
{
196 197
	struct omap_usb	*phy;
	struct phy *generic_phy;
198
	struct resource *res;
199 200 201 202 203
	struct phy_provider *phy_provider;
	struct usb_otg *otg;
	struct device_node *node = pdev->dev.of_node;
	struct device_node *control_node;
	struct platform_device *control_pdev;
204 205 206 207
	const struct of_device_id *of_id;
	struct usb_phy_data *phy_data;

	of_id = of_match_device(of_match_ptr(omap_usb2_id_table), &pdev->dev);
208

209
	if (!of_id)
210
		return -EINVAL;
211

212 213
	phy_data = (struct usb_phy_data *)of_id->data;

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
	phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
	if (!phy) {
		dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n");
		return -ENOMEM;
	}

	otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
	if (!otg) {
		dev_err(&pdev->dev, "unable to allocate memory for USB OTG\n");
		return -ENOMEM;
	}

	phy->dev		= &pdev->dev;

	phy->phy.dev		= phy->dev;
229
	phy->phy.label		= phy_data->label;
230
	phy->phy.otg		= otg;
231
	phy->phy.type		= USB_PHY_TYPE_USB2;
232

233 234 235 236 237 238 239 240
	if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
		phy->phy_base = devm_ioremap_resource(&pdev->dev, res);
		if (!phy->phy_base)
			return -ENOMEM;
		phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT;
	}

241 242 243 244
	control_node = of_parse_phandle(node, "ctrl-module", 0);
	if (!control_node) {
		dev_err(&pdev->dev, "Failed to get control device phandle\n");
		return -EINVAL;
245 246
	}

247 248 249 250 251 252 253
	control_pdev = of_find_device_by_node(control_node);
	if (!control_pdev) {
		dev_err(&pdev->dev, "Failed to get control device\n");
		return -EINVAL;
	}

	phy->control_dev = &control_pdev->dev;
254
	omap_control_phy_power(phy->control_dev, 0);
255 256 257

	otg->set_host		= omap_usb_set_host;
	otg->set_peripheral	= omap_usb_set_peripheral;
258 259 260 261
	if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS)
		otg->set_vbus		= omap_usb_set_vbus;
	if (phy_data->flags & OMAP_USB2_HAS_START_SRP)
		otg->start_srp		= omap_usb_start_srp;
262 263
	otg->phy		= &phy->phy;

264 265 266 267 268 269 270 271 272
	platform_set_drvdata(pdev, phy);
	pm_runtime_enable(phy->dev);

	generic_phy = devm_phy_create(phy->dev, &ops, NULL);
	if (IS_ERR(generic_phy))
		return PTR_ERR(generic_phy);

	phy_set_drvdata(generic_phy, phy);

273 274 275 276 277
	phy_provider = devm_of_phy_provider_register(phy->dev,
			of_phy_simple_xlate);
	if (IS_ERR(phy_provider))
		return PTR_ERR(phy_provider);

278 279 280 281 282 283 284
	phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k");
	if (IS_ERR(phy->wkupclk)) {
		dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n");
		return PTR_ERR(phy->wkupclk);
	}
	clk_prepare(phy->wkupclk);

285 286 287 288 289 290
	phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m");
	if (IS_ERR(phy->optclk))
		dev_vdbg(&pdev->dev, "unable to get refclk960m\n");
	else
		clk_prepare(phy->optclk);

291
	usb_add_phy_dev(&phy->phy);
292 293 294 295

	return 0;
}

B
Bill Pemberton 已提交
296
static int omap_usb2_remove(struct platform_device *pdev)
297 298 299 300
{
	struct omap_usb	*phy = platform_get_drvdata(pdev);

	clk_unprepare(phy->wkupclk);
301 302
	if (!IS_ERR(phy->optclk))
		clk_unprepare(phy->optclk);
303 304 305 306 307 308 309 310 311 312 313 314 315
	usb_remove_phy(&phy->phy);

	return 0;
}

#ifdef CONFIG_PM_RUNTIME

static int omap_usb2_runtime_suspend(struct device *dev)
{
	struct platform_device	*pdev = to_platform_device(dev);
	struct omap_usb	*phy = platform_get_drvdata(pdev);

	clk_disable(phy->wkupclk);
316 317
	if (!IS_ERR(phy->optclk))
		clk_disable(phy->optclk);
318 319 320 321 322 323 324 325

	return 0;
}

static int omap_usb2_runtime_resume(struct device *dev)
{
	struct platform_device	*pdev = to_platform_device(dev);
	struct omap_usb	*phy = platform_get_drvdata(pdev);
326
	int ret;
327 328

	ret = clk_enable(phy->wkupclk);
329
	if (ret < 0) {
330
		dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret);
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
		goto err0;
	}

	if (!IS_ERR(phy->optclk)) {
		ret = clk_enable(phy->optclk);
		if (ret < 0) {
			dev_err(phy->dev, "Failed to enable optclk %d\n", ret);
			goto err1;
		}
	}

	return 0;

err1:
	clk_disable(phy->wkupclk);
346

347
err0:
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
	return ret;
}

static const struct dev_pm_ops omap_usb2_pm_ops = {
	SET_RUNTIME_PM_OPS(omap_usb2_runtime_suspend, omap_usb2_runtime_resume,
		NULL)
};

#define DEV_PM_OPS     (&omap_usb2_pm_ops)
#else
#define DEV_PM_OPS     NULL
#endif

static struct platform_driver omap_usb2_driver = {
	.probe		= omap_usb2_probe,
B
Bill Pemberton 已提交
363
	.remove		= omap_usb2_remove,
364 365 366 367 368 369 370 371 372 373 374 375 376 377
	.driver		= {
		.name	= "omap-usb2",
		.owner	= THIS_MODULE,
		.pm	= DEV_PM_OPS,
		.of_match_table = of_match_ptr(omap_usb2_id_table),
	},
};

module_platform_driver(omap_usb2_driver);

MODULE_ALIAS("platform: omap_usb2");
MODULE_AUTHOR("Texas Instruments Inc.");
MODULE_DESCRIPTION("OMAP USB2 phy driver");
MODULE_LICENSE("GPL v2");