forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This driver is a redo of my earlier attempt. It uses parts of the generic PHY driver and uses the new control driver for the register the phy needs to power on/off the phy. It also enables easy access for the wakeup register which is not yet implemented. The difference between the omap attempt is: - no static holding variable - one global visible function which exports a struct with callbacks to access the "control" registers. Signed-off-by: Sebastian Andrzej Siewior <[email protected]> Signed-off-by: Felipe Balbi <[email protected]>
- Loading branch information
Sebastian Andrzej Siewior
authored and
Felipe Balbi
committed
Aug 9, 2013
1 parent
53b6fc2
commit 3bb869c
Showing
5 changed files
with
270 additions
and
0 deletions.
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
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,21 @@ | ||
#ifndef _AM335x_PHY_CONTROL_H_ | ||
#define _AM335x_PHY_CONTROL_H_ | ||
|
||
struct phy_control { | ||
void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on); | ||
void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on); | ||
}; | ||
|
||
static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, bool on) | ||
{ | ||
phy_ctrl->phy_power(phy_ctrl, id, on); | ||
} | ||
|
||
static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on) | ||
{ | ||
phy_ctrl->phy_wkup(phy_ctrl, id, on); | ||
} | ||
|
||
struct phy_control *am335x_get_phy_control(struct device *dev); | ||
|
||
#endif |
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,136 @@ | ||
#include <linux/module.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/of.h> | ||
#include <linux/io.h> | ||
|
||
struct phy_control { | ||
void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on); | ||
void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on); | ||
}; | ||
|
||
struct am335x_control_usb { | ||
struct device *dev; | ||
void __iomem *phy_reg; | ||
void __iomem *wkup; | ||
spinlock_t lock; | ||
struct phy_control phy_ctrl; | ||
}; | ||
|
||
#define AM335X_USB0_CTRL 0x0 | ||
#define AM335X_USB1_CTRL 0x8 | ||
#define AM335x_USB_WKUP 0x0 | ||
|
||
#define USBPHY_CM_PWRDN (1 << 0) | ||
#define USBPHY_OTG_PWRDN (1 << 1) | ||
#define USBPHY_OTGVDET_EN (1 << 19) | ||
#define USBPHY_OTGSESSEND_EN (1 << 20) | ||
|
||
static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, bool on) | ||
{ | ||
struct am335x_control_usb *usb_ctrl; | ||
u32 val; | ||
u32 reg; | ||
|
||
usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl); | ||
|
||
switch (id) { | ||
case 0: | ||
reg = AM335X_USB0_CTRL; | ||
break; | ||
case 1: | ||
reg = AM335X_USB1_CTRL; | ||
break; | ||
default: | ||
__WARN(); | ||
return; | ||
} | ||
|
||
val = readl(usb_ctrl->phy_reg + reg); | ||
if (on) { | ||
val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN); | ||
val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN; | ||
} else { | ||
val |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN; | ||
} | ||
|
||
writel(val, usb_ctrl->phy_reg + reg); | ||
} | ||
|
||
static const struct phy_control ctrl_am335x = { | ||
.phy_power = am335x_phy_power, | ||
}; | ||
|
||
static const struct of_device_id omap_control_usb_id_table[] = { | ||
{ .compatible = "ti,am335x-usb-ctrl-module", .data = &ctrl_am335x }, | ||
{} | ||
}; | ||
MODULE_DEVICE_TABLE(of, omap_control_usb_id_table); | ||
|
||
static struct platform_driver am335x_control_driver; | ||
static int match(struct device *dev, void *data) | ||
{ | ||
struct device_node *node = (struct device_node *)data; | ||
return dev->of_node == node && | ||
dev->driver == &am335x_control_driver.driver; | ||
} | ||
|
||
struct phy_control *am335x_get_phy_control(struct device *dev) | ||
{ | ||
struct device_node *node; | ||
struct am335x_control_usb *ctrl_usb; | ||
|
||
node = of_parse_phandle(dev->of_node, "ti,ctrl_mod", 0); | ||
if (!node) | ||
return NULL; | ||
|
||
dev = bus_find_device(&platform_bus_type, NULL, node, match); | ||
ctrl_usb = dev_get_drvdata(dev); | ||
if (!ctrl_usb) | ||
return NULL; | ||
return &ctrl_usb->phy_ctrl; | ||
} | ||
EXPORT_SYMBOL_GPL(am335x_get_phy_control); | ||
|
||
static int am335x_control_usb_probe(struct platform_device *pdev) | ||
{ | ||
struct resource *res; | ||
struct am335x_control_usb *ctrl_usb; | ||
const struct of_device_id *of_id; | ||
const struct phy_control *phy_ctrl; | ||
|
||
of_id = of_match_node(omap_control_usb_id_table, pdev->dev.of_node); | ||
if (!of_id) | ||
return -EINVAL; | ||
|
||
phy_ctrl = of_id->data; | ||
|
||
ctrl_usb = devm_kzalloc(&pdev->dev, sizeof(*ctrl_usb), GFP_KERNEL); | ||
if (!ctrl_usb) { | ||
dev_err(&pdev->dev, "unable to alloc memory for control usb\n"); | ||
return -ENOMEM; | ||
} | ||
|
||
ctrl_usb->dev = &pdev->dev; | ||
|
||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); | ||
ctrl_usb->phy_reg = devm_ioremap_resource(&pdev->dev, res); | ||
if (IS_ERR(ctrl_usb->phy_reg)) | ||
return PTR_ERR(ctrl_usb->phy_reg); | ||
spin_lock_init(&ctrl_usb->lock); | ||
ctrl_usb->phy_ctrl = *phy_ctrl; | ||
|
||
dev_set_drvdata(ctrl_usb->dev, ctrl_usb); | ||
return 0; | ||
} | ||
|
||
static struct platform_driver am335x_control_driver = { | ||
.probe = am335x_control_usb_probe, | ||
.driver = { | ||
.name = "am335x-control-usb", | ||
.owner = THIS_MODULE, | ||
.of_match_table = of_match_ptr(omap_control_usb_id_table), | ||
}, | ||
}; | ||
|
||
module_platform_driver(am335x_control_driver); | ||
MODULE_LICENSE("GPL v2"); |
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,99 @@ | ||
#include <linux/module.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/dma-mapping.h> | ||
#include <linux/usb/otg.h> | ||
#include <linux/usb/usb_phy_gen_xceiv.h> | ||
#include <linux/slab.h> | ||
#include <linux/clk.h> | ||
#include <linux/regulator/consumer.h> | ||
#include <linux/of.h> | ||
#include <linux/of_address.h> | ||
|
||
#include "am35x-phy-control.h" | ||
#include "phy-generic.h" | ||
|
||
struct am335x_phy { | ||
struct usb_phy_gen_xceiv usb_phy_gen; | ||
struct phy_control *phy_ctrl; | ||
int id; | ||
}; | ||
|
||
static int am335x_init(struct usb_phy *phy) | ||
{ | ||
struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); | ||
|
||
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true); | ||
return 0; | ||
} | ||
|
||
static void am335x_shutdown(struct usb_phy *phy) | ||
{ | ||
struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); | ||
|
||
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false); | ||
} | ||
|
||
static int am335x_phy_probe(struct platform_device *pdev) | ||
{ | ||
struct am335x_phy *am_phy; | ||
struct device *dev = &pdev->dev; | ||
int ret; | ||
|
||
am_phy = devm_kzalloc(dev, sizeof(*am_phy), GFP_KERNEL); | ||
if (!am_phy) | ||
return -ENOMEM; | ||
|
||
am_phy->phy_ctrl = am335x_get_phy_control(dev); | ||
if (!am_phy->phy_ctrl) | ||
return -EPROBE_DEFER; | ||
am_phy->id = of_alias_get_id(pdev->dev.of_node, "phy"); | ||
if (am_phy->id < 0) { | ||
dev_err(&pdev->dev, "Missing PHY id: %d\n", am_phy->id); | ||
return am_phy->id; | ||
} | ||
|
||
ret = usb_phy_gen_create_phy(dev, &am_phy->usb_phy_gen, | ||
USB_PHY_TYPE_USB2, 0, false, false); | ||
if (ret) | ||
return ret; | ||
|
||
ret = usb_add_phy_dev(&am_phy->usb_phy_gen.phy); | ||
if (ret) | ||
goto err_add; | ||
am_phy->usb_phy_gen.phy.init = am335x_init; | ||
am_phy->usb_phy_gen.phy.shutdown = am335x_shutdown; | ||
|
||
platform_set_drvdata(pdev, am_phy); | ||
return 0; | ||
|
||
err_add: | ||
usb_phy_gen_cleanup_phy(&am_phy->usb_phy_gen); | ||
return ret; | ||
} | ||
|
||
static int am335x_phy_remove(struct platform_device *pdev) | ||
{ | ||
struct am335x_phy *am_phy = platform_get_drvdata(pdev); | ||
|
||
usb_remove_phy(&am_phy->usb_phy_gen.phy); | ||
return 0; | ||
} | ||
|
||
static const struct of_device_id am335x_phy_ids[] = { | ||
{ .compatible = "ti,am335x-usb-phy" }, | ||
{ } | ||
}; | ||
MODULE_DEVICE_TABLE(of, am335x_phy_ids); | ||
|
||
static struct platform_driver am335x_phy_driver = { | ||
.probe = am335x_phy_probe, | ||
.remove = am335x_phy_remove, | ||
.driver = { | ||
.name = "am335x-phy-driver", | ||
.owner = THIS_MODULE, | ||
.of_match_table = of_match_ptr(am335x_phy_ids), | ||
}, | ||
}; | ||
|
||
module_platform_driver(am335x_phy_driver); | ||
MODULE_LICENSE("GPL v2"); |