-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
yaml --- r: 204580 b: refs/heads/master c: 2249071 h: refs/heads/master v: v3
- Loading branch information
Lars-Peter Clausen
authored and
Ralf Baechle
committed
Aug 5, 2010
1 parent
f0f9cbb
commit aa3004f
Showing
4 changed files
with
283 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
--- | ||
refs/heads/master: 61bfbdb856879cff583fe53b2ab6ae907faedee7 | ||
refs/heads/master: 2249071b3e03747884d0781ab10b0b9ceac5756b |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
/* | ||
* Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> | ||
* | ||
* 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. | ||
* | ||
* You should have received a copy of the GNU General Public License along | ||
* with this program; if not, write to the Free Software Foundation, Inc., | ||
* 675 Mass Ave, Cambridge, MA 02139, USA. | ||
* | ||
*/ | ||
|
||
#include <linux/platform_device.h> | ||
#include <linux/clk.h> | ||
#include <linux/regulator/consumer.h> | ||
|
||
struct jz4740_ohci_hcd { | ||
struct ohci_hcd ohci_hcd; | ||
|
||
struct regulator *vbus; | ||
bool vbus_enabled; | ||
struct clk *clk; | ||
}; | ||
|
||
static inline struct jz4740_ohci_hcd *hcd_to_jz4740_hcd(struct usb_hcd *hcd) | ||
{ | ||
return (struct jz4740_ohci_hcd *)(hcd->hcd_priv); | ||
} | ||
|
||
static inline struct usb_hcd *jz4740_hcd_to_hcd(struct jz4740_ohci_hcd *jz4740_ohci) | ||
{ | ||
return container_of((void *)jz4740_ohci, struct usb_hcd, hcd_priv); | ||
} | ||
|
||
static int ohci_jz4740_start(struct usb_hcd *hcd) | ||
{ | ||
struct ohci_hcd *ohci = hcd_to_ohci(hcd); | ||
int ret; | ||
|
||
ret = ohci_init(ohci); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ohci->num_ports = 1; | ||
|
||
ret = ohci_run(ohci); | ||
if (ret < 0) { | ||
dev_err(hcd->self.controller, "Can not start %s", | ||
hcd->self.bus_name); | ||
ohci_stop(hcd); | ||
return ret; | ||
} | ||
return 0; | ||
} | ||
|
||
static int ohci_jz4740_set_vbus_power(struct jz4740_ohci_hcd *jz4740_ohci, | ||
bool enabled) | ||
{ | ||
int ret = 0; | ||
|
||
if (!jz4740_ohci->vbus) | ||
return 0; | ||
|
||
if (enabled && !jz4740_ohci->vbus_enabled) { | ||
ret = regulator_enable(jz4740_ohci->vbus); | ||
if (ret) | ||
dev_err(jz4740_hcd_to_hcd(jz4740_ohci)->self.controller, | ||
"Could not power vbus\n"); | ||
} else if (!enabled && jz4740_ohci->vbus_enabled) { | ||
ret = regulator_disable(jz4740_ohci->vbus); | ||
} | ||
|
||
if (ret == 0) | ||
jz4740_ohci->vbus_enabled = enabled; | ||
|
||
return ret; | ||
} | ||
|
||
static int ohci_jz4740_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | ||
u16 wIndex, char *buf, u16 wLength) | ||
{ | ||
struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd); | ||
int ret; | ||
|
||
switch (typeReq) { | ||
case SetHubFeature: | ||
if (wValue == USB_PORT_FEAT_POWER) | ||
ret = ohci_jz4740_set_vbus_power(jz4740_ohci, true); | ||
break; | ||
case ClearHubFeature: | ||
if (wValue == USB_PORT_FEAT_POWER) | ||
ret = ohci_jz4740_set_vbus_power(jz4740_ohci, false); | ||
break; | ||
} | ||
|
||
if (ret) | ||
return ret; | ||
|
||
return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); | ||
} | ||
|
||
|
||
static const struct hc_driver ohci_jz4740_hc_driver = { | ||
.description = hcd_name, | ||
.product_desc = "JZ4740 OHCI", | ||
.hcd_priv_size = sizeof(struct jz4740_ohci_hcd), | ||
|
||
/* | ||
* generic hardware linkage | ||
*/ | ||
.irq = ohci_irq, | ||
.flags = HCD_USB11 | HCD_MEMORY, | ||
|
||
/* | ||
* basic lifecycle operations | ||
*/ | ||
.start = ohci_jz4740_start, | ||
.stop = ohci_stop, | ||
.shutdown = ohci_shutdown, | ||
|
||
/* | ||
* managing i/o requests and associated device resources | ||
*/ | ||
.urb_enqueue = ohci_urb_enqueue, | ||
.urb_dequeue = ohci_urb_dequeue, | ||
.endpoint_disable = ohci_endpoint_disable, | ||
|
||
/* | ||
* scheduling support | ||
*/ | ||
.get_frame_number = ohci_get_frame, | ||
|
||
/* | ||
* root hub support | ||
*/ | ||
.hub_status_data = ohci_hub_status_data, | ||
.hub_control = ohci_jz4740_hub_control, | ||
#ifdef CONFIG_PM | ||
.bus_suspend = ohci_bus_suspend, | ||
.bus_resume = ohci_bus_resume, | ||
#endif | ||
.start_port_reset = ohci_start_port_reset, | ||
}; | ||
|
||
|
||
static __devinit int jz4740_ohci_probe(struct platform_device *pdev) | ||
{ | ||
int ret; | ||
struct usb_hcd *hcd; | ||
struct jz4740_ohci_hcd *jz4740_ohci; | ||
struct resource *res; | ||
int irq; | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
|
||
if (!res) { | ||
dev_err(&pdev->dev, "Failed to get platform resource\n"); | ||
return -ENOENT; | ||
} | ||
|
||
irq = platform_get_irq(pdev, 0); | ||
if (irq < 0) { | ||
dev_err(&pdev->dev, "Failed to get platform irq\n"); | ||
return irq; | ||
} | ||
|
||
hcd = usb_create_hcd(&ohci_jz4740_hc_driver, &pdev->dev, "jz4740"); | ||
if (!hcd) { | ||
dev_err(&pdev->dev, "Failed to create hcd.\n"); | ||
return -ENOMEM; | ||
} | ||
|
||
jz4740_ohci = hcd_to_jz4740_hcd(hcd); | ||
|
||
res = request_mem_region(res->start, resource_size(res), hcd_name); | ||
if (!res) { | ||
dev_err(&pdev->dev, "Failed to request mem region.\n"); | ||
ret = -EBUSY; | ||
goto err_free; | ||
} | ||
|
||
hcd->rsrc_start = res->start; | ||
hcd->rsrc_len = resource_size(res); | ||
hcd->regs = ioremap(res->start, resource_size(res)); | ||
|
||
if (!hcd->regs) { | ||
dev_err(&pdev->dev, "Failed to ioremap registers.\n"); | ||
ret = -EBUSY; | ||
goto err_release_mem; | ||
} | ||
|
||
jz4740_ohci->clk = clk_get(&pdev->dev, "uhc"); | ||
if (IS_ERR(jz4740_ohci->clk)) { | ||
ret = PTR_ERR(jz4740_ohci->clk); | ||
dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); | ||
goto err_iounmap; | ||
} | ||
|
||
jz4740_ohci->vbus = regulator_get(&pdev->dev, "vbus"); | ||
if (IS_ERR(jz4740_ohci->vbus)) | ||
jz4740_ohci->vbus = NULL; | ||
|
||
|
||
clk_set_rate(jz4740_ohci->clk, 48000000); | ||
clk_enable(jz4740_ohci->clk); | ||
if (jz4740_ohci->vbus) | ||
ohci_jz4740_set_vbus_power(jz4740_ohci, true); | ||
|
||
platform_set_drvdata(pdev, hcd); | ||
|
||
ohci_hcd_init(hcd_to_ohci(hcd)); | ||
|
||
ret = usb_add_hcd(hcd, irq, 0); | ||
if (ret) { | ||
dev_err(&pdev->dev, "Failed to add hcd: %d\n", ret); | ||
goto err_disable; | ||
} | ||
|
||
return 0; | ||
|
||
err_disable: | ||
platform_set_drvdata(pdev, NULL); | ||
if (jz4740_ohci->vbus) { | ||
regulator_disable(jz4740_ohci->vbus); | ||
regulator_put(jz4740_ohci->vbus); | ||
} | ||
clk_disable(jz4740_ohci->clk); | ||
|
||
clk_put(jz4740_ohci->clk); | ||
err_iounmap: | ||
iounmap(hcd->regs); | ||
err_release_mem: | ||
release_mem_region(res->start, resource_size(res)); | ||
err_free: | ||
usb_put_hcd(hcd); | ||
|
||
return ret; | ||
} | ||
|
||
static __devexit int jz4740_ohci_remove(struct platform_device *pdev) | ||
{ | ||
struct usb_hcd *hcd = platform_get_drvdata(pdev); | ||
struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd); | ||
|
||
usb_remove_hcd(hcd); | ||
|
||
platform_set_drvdata(pdev, NULL); | ||
|
||
if (jz4740_ohci->vbus) { | ||
regulator_disable(jz4740_ohci->vbus); | ||
regulator_put(jz4740_ohci->vbus); | ||
} | ||
|
||
clk_disable(jz4740_ohci->clk); | ||
clk_put(jz4740_ohci->clk); | ||
|
||
iounmap(hcd->regs); | ||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len); | ||
|
||
usb_put_hcd(hcd); | ||
|
||
return 0; | ||
} | ||
|
||
static struct platform_driver ohci_hcd_jz4740_driver = { | ||
.probe = jz4740_ohci_probe, | ||
.remove = __devexit_p(jz4740_ohci_remove), | ||
.driver = { | ||
.name = "jz4740-ohci", | ||
.owner = THIS_MODULE, | ||
}, | ||
}; | ||
|
||
MODULE_ALIAS("platfrom:jz4740-ohci"); |