| Neil Zhang | 3a082ec | 2011-12-20 13:20:23 +0800 | [diff] [blame] | 1 | /* | 
 | 2 |  * Copyright (C) 2011 Marvell International Ltd. All rights reserved. | 
 | 3 |  * Author: Chao Xie <chao.xie@marvell.com> | 
 | 4 |  *        Neil Zhang <zhangwm@marvell.com> | 
 | 5 |  * | 
 | 6 |  * This program is free software; you can redistribute  it and/or modify it | 
 | 7 |  * under  the terms of  the GNU General  Public License as published by the | 
 | 8 |  * Free Software Foundation;  either version 2 of the  License, or (at your | 
 | 9 |  * option) any later version. | 
 | 10 |  */ | 
 | 11 |  | 
 | 12 | #include <linux/kernel.h> | 
 | 13 | #include <linux/module.h> | 
 | 14 | #include <linux/platform_device.h> | 
 | 15 | #include <linux/clk.h> | 
 | 16 | #include <linux/usb/otg.h> | 
 | 17 | #include <linux/platform_data/mv_usb.h> | 
 | 18 |  | 
 | 19 | #define CAPLENGTH_MASK         (0xff) | 
 | 20 |  | 
 | 21 | struct ehci_hcd_mv { | 
 | 22 | 	struct usb_hcd *hcd; | 
 | 23 |  | 
 | 24 | 	/* Which mode does this ehci running OTG/Host ? */ | 
 | 25 | 	int mode; | 
 | 26 |  | 
 | 27 | 	void __iomem *phy_regs; | 
 | 28 | 	void __iomem *cap_regs; | 
 | 29 | 	void __iomem *op_regs; | 
 | 30 |  | 
| Heikki Krogerus | 8675381 | 2012-02-13 13:24:02 +0200 | [diff] [blame] | 31 | 	struct usb_phy *otg; | 
| Neil Zhang | 3a082ec | 2011-12-20 13:20:23 +0800 | [diff] [blame] | 32 |  | 
 | 33 | 	struct mv_usb_platform_data *pdata; | 
 | 34 |  | 
 | 35 | 	/* clock source and total clock number */ | 
 | 36 | 	unsigned int clknum; | 
 | 37 | 	struct clk *clk[0]; | 
 | 38 | }; | 
 | 39 |  | 
 | 40 | static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) | 
 | 41 | { | 
 | 42 | 	unsigned int i; | 
 | 43 |  | 
 | 44 | 	for (i = 0; i < ehci_mv->clknum; i++) | 
 | 45 | 		clk_enable(ehci_mv->clk[i]); | 
 | 46 | } | 
 | 47 |  | 
 | 48 | static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) | 
 | 49 | { | 
 | 50 | 	unsigned int i; | 
 | 51 |  | 
 | 52 | 	for (i = 0; i < ehci_mv->clknum; i++) | 
 | 53 | 		clk_disable(ehci_mv->clk[i]); | 
 | 54 | } | 
 | 55 |  | 
 | 56 | static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) | 
 | 57 | { | 
 | 58 | 	int retval; | 
 | 59 |  | 
 | 60 | 	ehci_clock_enable(ehci_mv); | 
 | 61 | 	if (ehci_mv->pdata->phy_init) { | 
 | 62 | 		retval = ehci_mv->pdata->phy_init(ehci_mv->phy_regs); | 
 | 63 | 		if (retval) | 
 | 64 | 			return retval; | 
 | 65 | 	} | 
 | 66 |  | 
 | 67 | 	return 0; | 
 | 68 | } | 
 | 69 |  | 
 | 70 | static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) | 
 | 71 | { | 
 | 72 | 	if (ehci_mv->pdata->phy_deinit) | 
 | 73 | 		ehci_mv->pdata->phy_deinit(ehci_mv->phy_regs); | 
 | 74 | 	ehci_clock_disable(ehci_mv); | 
 | 75 | } | 
 | 76 |  | 
 | 77 | static int mv_ehci_reset(struct usb_hcd *hcd) | 
 | 78 | { | 
 | 79 | 	struct ehci_hcd *ehci = hcd_to_ehci(hcd); | 
 | 80 | 	struct device *dev = hcd->self.controller; | 
 | 81 | 	struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev); | 
 | 82 | 	int retval; | 
 | 83 |  | 
 | 84 | 	if (ehci_mv == NULL) { | 
 | 85 | 		dev_err(dev, "Can not find private ehci data\n"); | 
 | 86 | 		return -ENODEV; | 
 | 87 | 	} | 
 | 88 |  | 
 | 89 | 	/* | 
 | 90 | 	 * data structure init | 
 | 91 | 	 */ | 
 | 92 | 	retval = ehci_init(hcd); | 
 | 93 | 	if (retval) { | 
 | 94 | 		dev_err(dev, "ehci_init failed %d\n", retval); | 
 | 95 | 		return retval; | 
 | 96 | 	} | 
 | 97 |  | 
 | 98 | 	hcd->has_tt = 1; | 
 | 99 | 	ehci->sbrn = 0x20; | 
 | 100 |  | 
 | 101 | 	retval = ehci_reset(ehci); | 
 | 102 | 	if (retval) { | 
 | 103 | 		dev_err(dev, "ehci_reset failed %d\n", retval); | 
 | 104 | 		return retval; | 
 | 105 | 	} | 
 | 106 |  | 
 | 107 | 	return 0; | 
 | 108 | } | 
 | 109 |  | 
 | 110 | static const struct hc_driver mv_ehci_hc_driver = { | 
 | 111 | 	.description = hcd_name, | 
 | 112 | 	.product_desc = "Marvell EHCI", | 
 | 113 | 	.hcd_priv_size = sizeof(struct ehci_hcd), | 
 | 114 |  | 
 | 115 | 	/* | 
 | 116 | 	 * generic hardware linkage | 
 | 117 | 	 */ | 
 | 118 | 	.irq = ehci_irq, | 
 | 119 | 	.flags = HCD_MEMORY | HCD_USB2, | 
 | 120 |  | 
 | 121 | 	/* | 
 | 122 | 	 * basic lifecycle operations | 
 | 123 | 	 */ | 
 | 124 | 	.reset = mv_ehci_reset, | 
 | 125 | 	.start = ehci_run, | 
 | 126 | 	.stop = ehci_stop, | 
 | 127 | 	.shutdown = ehci_shutdown, | 
 | 128 |  | 
 | 129 | 	/* | 
 | 130 | 	 * managing i/o requests and associated device resources | 
 | 131 | 	 */ | 
 | 132 | 	.urb_enqueue = ehci_urb_enqueue, | 
 | 133 | 	.urb_dequeue = ehci_urb_dequeue, | 
 | 134 | 	.endpoint_disable = ehci_endpoint_disable, | 
 | 135 | 	.endpoint_reset = ehci_endpoint_reset, | 
 | 136 | 	.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, | 
 | 137 |  | 
 | 138 | 	/* | 
 | 139 | 	 * scheduling support | 
 | 140 | 	 */ | 
 | 141 | 	.get_frame_number = ehci_get_frame, | 
 | 142 |  | 
 | 143 | 	/* | 
 | 144 | 	 * root hub support | 
 | 145 | 	 */ | 
 | 146 | 	.hub_status_data = ehci_hub_status_data, | 
 | 147 | 	.hub_control = ehci_hub_control, | 
 | 148 | 	.bus_suspend = ehci_bus_suspend, | 
 | 149 | 	.bus_resume = ehci_bus_resume, | 
 | 150 | }; | 
 | 151 |  | 
 | 152 | static int mv_ehci_probe(struct platform_device *pdev) | 
 | 153 | { | 
 | 154 | 	struct mv_usb_platform_data *pdata = pdev->dev.platform_data; | 
 | 155 | 	struct usb_hcd *hcd; | 
 | 156 | 	struct ehci_hcd *ehci; | 
 | 157 | 	struct ehci_hcd_mv *ehci_mv; | 
 | 158 | 	struct resource *r; | 
 | 159 | 	int clk_i, retval = -ENODEV; | 
 | 160 | 	u32 offset; | 
 | 161 | 	size_t size; | 
 | 162 |  | 
 | 163 | 	if (!pdata) { | 
 | 164 | 		dev_err(&pdev->dev, "missing platform_data\n"); | 
 | 165 | 		return -ENODEV; | 
 | 166 | 	} | 
 | 167 |  | 
 | 168 | 	if (usb_disabled()) | 
 | 169 | 		return -ENODEV; | 
 | 170 |  | 
 | 171 | 	hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci"); | 
 | 172 | 	if (!hcd) | 
 | 173 | 		return -ENOMEM; | 
 | 174 |  | 
 | 175 | 	size = sizeof(*ehci_mv) + sizeof(struct clk *) * pdata->clknum; | 
 | 176 | 	ehci_mv = kzalloc(size, GFP_KERNEL); | 
 | 177 | 	if (ehci_mv == NULL) { | 
 | 178 | 		dev_err(&pdev->dev, "cannot allocate ehci_hcd_mv\n"); | 
 | 179 | 		retval = -ENOMEM; | 
 | 180 | 		goto err_put_hcd; | 
 | 181 | 	} | 
 | 182 |  | 
 | 183 | 	platform_set_drvdata(pdev, ehci_mv); | 
 | 184 | 	ehci_mv->pdata = pdata; | 
 | 185 | 	ehci_mv->hcd = hcd; | 
 | 186 |  | 
 | 187 | 	ehci_mv->clknum = pdata->clknum; | 
 | 188 | 	for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++) { | 
 | 189 | 		ehci_mv->clk[clk_i] = | 
 | 190 | 		    clk_get(&pdev->dev, pdata->clkname[clk_i]); | 
 | 191 | 		if (IS_ERR(ehci_mv->clk[clk_i])) { | 
 | 192 | 			dev_err(&pdev->dev, "error get clck \"%s\"\n", | 
 | 193 | 				pdata->clkname[clk_i]); | 
 | 194 | 			retval = PTR_ERR(ehci_mv->clk[clk_i]); | 
 | 195 | 			goto err_put_clk; | 
 | 196 | 		} | 
 | 197 | 	} | 
 | 198 |  | 
 | 199 | 	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); | 
 | 200 | 	if (r == NULL) { | 
 | 201 | 		dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); | 
 | 202 | 		retval = -ENODEV; | 
 | 203 | 		goto err_put_clk; | 
 | 204 | 	} | 
 | 205 |  | 
 | 206 | 	ehci_mv->phy_regs = ioremap(r->start, resource_size(r)); | 
 | 207 | 	if (ehci_mv->phy_regs == 0) { | 
 | 208 | 		dev_err(&pdev->dev, "failed to map phy I/O memory\n"); | 
 | 209 | 		retval = -EFAULT; | 
 | 210 | 		goto err_put_clk; | 
 | 211 | 	} | 
 | 212 |  | 
 | 213 | 	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); | 
 | 214 | 	if (!r) { | 
 | 215 | 		dev_err(&pdev->dev, "no I/O memory resource defined\n"); | 
 | 216 | 		retval = -ENODEV; | 
 | 217 | 		goto err_iounmap_phyreg; | 
 | 218 | 	} | 
 | 219 |  | 
 | 220 | 	ehci_mv->cap_regs = ioremap(r->start, resource_size(r)); | 
 | 221 | 	if (ehci_mv->cap_regs == NULL) { | 
 | 222 | 		dev_err(&pdev->dev, "failed to map I/O memory\n"); | 
 | 223 | 		retval = -EFAULT; | 
 | 224 | 		goto err_iounmap_phyreg; | 
 | 225 | 	} | 
 | 226 |  | 
 | 227 | 	retval = mv_ehci_enable(ehci_mv); | 
 | 228 | 	if (retval) { | 
 | 229 | 		dev_err(&pdev->dev, "init phy error %d\n", retval); | 
 | 230 | 		goto err_iounmap_capreg; | 
 | 231 | 	} | 
 | 232 |  | 
 | 233 | 	offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; | 
 | 234 | 	ehci_mv->op_regs = | 
 | 235 | 		(void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); | 
 | 236 |  | 
 | 237 | 	hcd->rsrc_start = r->start; | 
 | 238 | 	hcd->rsrc_len = r->end - r->start + 1; | 
 | 239 | 	hcd->regs = ehci_mv->op_regs; | 
 | 240 |  | 
 | 241 | 	hcd->irq = platform_get_irq(pdev, 0); | 
 | 242 | 	if (!hcd->irq) { | 
 | 243 | 		dev_err(&pdev->dev, "Cannot get irq."); | 
 | 244 | 		retval = -ENODEV; | 
 | 245 | 		goto err_disable_clk; | 
 | 246 | 	} | 
 | 247 |  | 
 | 248 | 	ehci = hcd_to_ehci(hcd); | 
 | 249 | 	ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs; | 
 | 250 | 	ehci->regs = (struct ehci_regs *) ehci_mv->op_regs; | 
 | 251 | 	ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); | 
 | 252 |  | 
 | 253 | 	ehci_mv->mode = pdata->mode; | 
 | 254 | 	if (ehci_mv->mode == MV_USB_MODE_OTG) { | 
 | 255 | #ifdef CONFIG_USB_OTG_UTILS | 
| Heikki Krogerus | b96d3b0 | 2012-02-13 13:24:18 +0200 | [diff] [blame] | 256 | 		ehci_mv->otg = usb_get_transceiver(); | 
| Neil Zhang | 3a082ec | 2011-12-20 13:20:23 +0800 | [diff] [blame] | 257 | 		if (!ehci_mv->otg) { | 
 | 258 | 			dev_err(&pdev->dev, | 
 | 259 | 				"unable to find transceiver\n"); | 
 | 260 | 			retval = -ENODEV; | 
 | 261 | 			goto err_disable_clk; | 
 | 262 | 		} | 
 | 263 |  | 
| Heikki Krogerus | 6e13c65 | 2012-02-13 13:24:20 +0200 | [diff] [blame] | 264 | 		retval = otg_set_host(ehci_mv->otg->otg, &hcd->self); | 
| Neil Zhang | 3a082ec | 2011-12-20 13:20:23 +0800 | [diff] [blame] | 265 | 		if (retval < 0) { | 
 | 266 | 			dev_err(&pdev->dev, | 
 | 267 | 				"unable to register with transceiver\n"); | 
 | 268 | 			retval = -ENODEV; | 
 | 269 | 			goto err_put_transceiver; | 
 | 270 | 		} | 
 | 271 | 		/* otg will enable clock before use as host */ | 
 | 272 | 		mv_ehci_disable(ehci_mv); | 
 | 273 | #else | 
 | 274 | 		dev_info(&pdev->dev, "MV_USB_MODE_OTG " | 
 | 275 | 			 "must have CONFIG_USB_OTG_UTILS enabled\n"); | 
 | 276 | 		goto err_disable_clk; | 
 | 277 | #endif | 
 | 278 | 	} else { | 
 | 279 | 		if (pdata->set_vbus) | 
 | 280 | 			pdata->set_vbus(1); | 
 | 281 |  | 
 | 282 | 		retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); | 
 | 283 | 		if (retval) { | 
 | 284 | 			dev_err(&pdev->dev, | 
 | 285 | 				"failed to add hcd with err %d\n", retval); | 
 | 286 | 			goto err_set_vbus; | 
 | 287 | 		} | 
 | 288 | 	} | 
 | 289 |  | 
 | 290 | 	if (pdata->private_init) | 
 | 291 | 		pdata->private_init(ehci_mv->op_regs, ehci_mv->phy_regs); | 
 | 292 |  | 
 | 293 | 	dev_info(&pdev->dev, | 
 | 294 | 		 "successful find EHCI device with regs 0x%p irq %d" | 
 | 295 | 		 " working in %s mode\n", hcd->regs, hcd->irq, | 
 | 296 | 		 ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host"); | 
 | 297 |  | 
 | 298 | 	return 0; | 
 | 299 |  | 
 | 300 | err_set_vbus: | 
 | 301 | 	if (pdata->set_vbus) | 
 | 302 | 		pdata->set_vbus(0); | 
 | 303 | #ifdef CONFIG_USB_OTG_UTILS | 
 | 304 | err_put_transceiver: | 
 | 305 | 	if (ehci_mv->otg) | 
| Heikki Krogerus | b96d3b0 | 2012-02-13 13:24:18 +0200 | [diff] [blame] | 306 | 		usb_put_transceiver(ehci_mv->otg); | 
| Neil Zhang | 3a082ec | 2011-12-20 13:20:23 +0800 | [diff] [blame] | 307 | #endif | 
 | 308 | err_disable_clk: | 
 | 309 | 	mv_ehci_disable(ehci_mv); | 
 | 310 | err_iounmap_capreg: | 
 | 311 | 	iounmap(ehci_mv->cap_regs); | 
 | 312 | err_iounmap_phyreg: | 
 | 313 | 	iounmap(ehci_mv->phy_regs); | 
 | 314 | err_put_clk: | 
 | 315 | 	for (clk_i--; clk_i >= 0; clk_i--) | 
 | 316 | 		clk_put(ehci_mv->clk[clk_i]); | 
 | 317 | 	platform_set_drvdata(pdev, NULL); | 
 | 318 | 	kfree(ehci_mv); | 
 | 319 | err_put_hcd: | 
 | 320 | 	usb_put_hcd(hcd); | 
 | 321 |  | 
 | 322 | 	return retval; | 
 | 323 | } | 
 | 324 |  | 
 | 325 | static int mv_ehci_remove(struct platform_device *pdev) | 
 | 326 | { | 
 | 327 | 	struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); | 
 | 328 | 	struct usb_hcd *hcd = ehci_mv->hcd; | 
 | 329 | 	int clk_i; | 
 | 330 |  | 
 | 331 | 	if (hcd->rh_registered) | 
 | 332 | 		usb_remove_hcd(hcd); | 
 | 333 |  | 
 | 334 | 	if (ehci_mv->otg) { | 
| Heikki Krogerus | 6e13c65 | 2012-02-13 13:24:20 +0200 | [diff] [blame] | 335 | 		otg_set_host(ehci_mv->otg->otg, NULL); | 
| Heikki Krogerus | b96d3b0 | 2012-02-13 13:24:18 +0200 | [diff] [blame] | 336 | 		usb_put_transceiver(ehci_mv->otg); | 
| Neil Zhang | 3a082ec | 2011-12-20 13:20:23 +0800 | [diff] [blame] | 337 | 	} | 
 | 338 |  | 
 | 339 | 	if (ehci_mv->mode == MV_USB_MODE_HOST) { | 
 | 340 | 		if (ehci_mv->pdata->set_vbus) | 
 | 341 | 			ehci_mv->pdata->set_vbus(0); | 
 | 342 |  | 
 | 343 | 		mv_ehci_disable(ehci_mv); | 
 | 344 | 	} | 
 | 345 |  | 
 | 346 | 	iounmap(ehci_mv->cap_regs); | 
 | 347 | 	iounmap(ehci_mv->phy_regs); | 
 | 348 |  | 
 | 349 | 	for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++) | 
 | 350 | 		clk_put(ehci_mv->clk[clk_i]); | 
 | 351 |  | 
 | 352 | 	platform_set_drvdata(pdev, NULL); | 
 | 353 |  | 
 | 354 | 	kfree(ehci_mv); | 
 | 355 | 	usb_put_hcd(hcd); | 
 | 356 |  | 
 | 357 | 	return 0; | 
 | 358 | } | 
 | 359 |  | 
 | 360 | MODULE_ALIAS("mv-ehci"); | 
 | 361 |  | 
 | 362 | static const struct platform_device_id ehci_id_table[] = { | 
 | 363 | 	{"pxa-u2oehci", PXA_U2OEHCI}, | 
 | 364 | 	{"pxa-sph", PXA_SPH}, | 
 | 365 | 	{"mmp3-hsic", MMP3_HSIC}, | 
 | 366 | 	{"mmp3-fsic", MMP3_FSIC}, | 
 | 367 | 	{}, | 
 | 368 | }; | 
 | 369 |  | 
 | 370 | static void mv_ehci_shutdown(struct platform_device *pdev) | 
 | 371 | { | 
 | 372 | 	struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); | 
 | 373 | 	struct usb_hcd *hcd = ehci_mv->hcd; | 
 | 374 |  | 
 | 375 | 	if (!hcd->rh_registered) | 
 | 376 | 		return; | 
 | 377 |  | 
 | 378 | 	if (hcd->driver->shutdown) | 
 | 379 | 		hcd->driver->shutdown(hcd); | 
 | 380 | } | 
 | 381 |  | 
 | 382 | static struct platform_driver ehci_mv_driver = { | 
 | 383 | 	.probe = mv_ehci_probe, | 
 | 384 | 	.remove = mv_ehci_remove, | 
 | 385 | 	.shutdown = mv_ehci_shutdown, | 
 | 386 | 	.driver = { | 
 | 387 | 		   .name = "mv-ehci", | 
 | 388 | 		   .bus = &platform_bus_type, | 
 | 389 | 		   }, | 
 | 390 | 	.id_table = ehci_id_table, | 
 | 391 | }; |