usb: phy: msm: Add device tree support and binding information
Allows controller to be specified via device tree. Signed-off-by: Ivan T. Ivanov <iivanov@mm-sol.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
parent
f60c114a3a
commit
8364f9af23
@ -15,3 +15,70 @@ Example EHCI controller device node:
|
|||||||
usb-phy = <&usb_otg>;
|
usb-phy = <&usb_otg>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
USB PHY with optional OTG:
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Should contain:
|
||||||
|
"qcom,usb-otg-ci" for chipsets with ChipIdea 45nm PHY
|
||||||
|
"qcom,usb-otg-snps" for chipsets with Synopsys 28nm PHY
|
||||||
|
|
||||||
|
- regs: Offset and length of the register set in the memory map
|
||||||
|
- interrupts: interrupt-specifier for the OTG interrupt.
|
||||||
|
|
||||||
|
- clocks: A list of phandle + clock-specifier pairs for the
|
||||||
|
clocks listed in clock-names
|
||||||
|
- clock-names: Should contain the following:
|
||||||
|
"phy" USB PHY reference clock
|
||||||
|
"core" Protocol engine clock
|
||||||
|
"iface" Interface bus clock
|
||||||
|
"alt_core" Protocol engine clock for targets with asynchronous
|
||||||
|
reset methodology. (optional)
|
||||||
|
|
||||||
|
- vdccx-supply: phandle to the regulator for the vdd supply for
|
||||||
|
digital circuit operation.
|
||||||
|
- v1p8-supply: phandle to the regulator for the 1.8V supply
|
||||||
|
- v3p3-supply: phandle to the regulator for the 3.3V supply
|
||||||
|
|
||||||
|
- resets: A list of phandle + reset-specifier pairs for the
|
||||||
|
resets listed in reset-names
|
||||||
|
- reset-names: Should contain the following:
|
||||||
|
"phy" USB PHY controller reset
|
||||||
|
"link" USB LINK controller reset
|
||||||
|
|
||||||
|
- qcom,otg-control: OTG control (VBUS and ID notifications) can be one of
|
||||||
|
1 - PHY control
|
||||||
|
2 - PMIC control
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
- dr_mode: One of "host", "peripheral" or "otg". Defaults to "otg"
|
||||||
|
|
||||||
|
- qcom,phy-init-sequence: PHY configuration sequence values. This is related to Device
|
||||||
|
Mode Eye Diagram test. Start address at which these values will be
|
||||||
|
written is ULPI_EXT_VENDOR_SPECIFIC. Value of -1 is reserved as
|
||||||
|
"do not overwrite default value at this address".
|
||||||
|
For example: qcom,phy-init-sequence = < -1 0x63 >;
|
||||||
|
Will update only value at address ULPI_EXT_VENDOR_SPECIFIC + 1.
|
||||||
|
|
||||||
|
Example HSUSB OTG controller device node:
|
||||||
|
|
||||||
|
usb@f9a55000 {
|
||||||
|
compatible = "qcom,usb-otg-snps";
|
||||||
|
reg = <0xf9a55000 0x400>;
|
||||||
|
interrupts = <0 134 0>;
|
||||||
|
dr_mode = "peripheral";
|
||||||
|
|
||||||
|
clocks = <&gcc GCC_XO_CLK>, <&gcc GCC_USB_HS_SYSTEM_CLK>,
|
||||||
|
<&gcc GCC_USB_HS_AHB_CLK>;
|
||||||
|
|
||||||
|
clock-names = "phy", "core", "iface";
|
||||||
|
|
||||||
|
vddcx-supply = <&pm8841_s2_corner>;
|
||||||
|
v1p8-supply = <&pm8941_l6>;
|
||||||
|
v3p3-supply = <&pm8941_l24>;
|
||||||
|
|
||||||
|
resets = <&gcc GCC_USB2A_PHY_BCR>, <&gcc GCC_USB_HS_BCR>;
|
||||||
|
reset-names = "phy", "link";
|
||||||
|
|
||||||
|
qcom,otg-control = <1>;
|
||||||
|
qcom,phy-init-sequence = < -1 0x63 >;
|
||||||
|
};
|
||||||
|
@ -30,9 +30,12 @@
|
|||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
#include <linux/seq_file.h>
|
#include <linux/seq_file.h>
|
||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
|
||||||
#include <linux/usb.h>
|
#include <linux/usb.h>
|
||||||
#include <linux/usb/otg.h>
|
#include <linux/usb/otg.h>
|
||||||
|
#include <linux/usb/of.h>
|
||||||
#include <linux/usb/ulpi.h>
|
#include <linux/usb/ulpi.h>
|
||||||
#include <linux/usb/gadget.h>
|
#include <linux/usb/gadget.h>
|
||||||
#include <linux/usb/hcd.h>
|
#include <linux/usb/hcd.h>
|
||||||
@ -217,16 +220,16 @@ static struct usb_phy_io_ops msm_otg_io_ops = {
|
|||||||
static void ulpi_init(struct msm_otg *motg)
|
static void ulpi_init(struct msm_otg *motg)
|
||||||
{
|
{
|
||||||
struct msm_otg_platform_data *pdata = motg->pdata;
|
struct msm_otg_platform_data *pdata = motg->pdata;
|
||||||
int *seq = pdata->phy_init_seq;
|
int *seq = pdata->phy_init_seq, idx;
|
||||||
|
u32 addr = ULPI_EXT_VENDOR_SPECIFIC;
|
||||||
|
|
||||||
if (!seq)
|
for (idx = 0; idx < pdata->phy_init_sz; idx++) {
|
||||||
return;
|
if (seq[idx] == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
while (seq[0] >= 0) {
|
|
||||||
dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n",
|
dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n",
|
||||||
seq[0], seq[1]);
|
seq[idx], addr + idx);
|
||||||
ulpi_write(&motg->phy, seq[0], seq[1]);
|
ulpi_write(&motg->phy, seq[idx], addr + idx);
|
||||||
seq += 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1343,26 +1346,96 @@ static void msm_otg_debugfs_cleanup(void)
|
|||||||
debugfs_remove(msm_otg_dbg_root);
|
debugfs_remove(msm_otg_dbg_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct of_device_id msm_otg_dt_match[] = {
|
||||||
|
{
|
||||||
|
.compatible = "qcom,usb-otg-ci",
|
||||||
|
.data = (void *) CI_45NM_INTEGRATED_PHY
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.compatible = "qcom,usb-otg-snps",
|
||||||
|
.data = (void *) SNPS_28NM_INTEGRATED_PHY
|
||||||
|
},
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, msm_otg_dt_match);
|
||||||
|
|
||||||
|
static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg)
|
||||||
|
{
|
||||||
|
struct msm_otg_platform_data *pdata;
|
||||||
|
const struct of_device_id *id;
|
||||||
|
struct device_node *node = pdev->dev.of_node;
|
||||||
|
struct property *prop;
|
||||||
|
int len, ret, words;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||||
|
if (!pdata)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
motg->pdata = pdata;
|
||||||
|
|
||||||
|
id = of_match_device(msm_otg_dt_match, &pdev->dev);
|
||||||
|
pdata->phy_type = (int) id->data;
|
||||||
|
|
||||||
|
pdata->mode = of_usb_get_dr_mode(node);
|
||||||
|
if (pdata->mode == USB_DR_MODE_UNKNOWN)
|
||||||
|
pdata->mode = USB_DR_MODE_OTG;
|
||||||
|
|
||||||
|
pdata->otg_control = OTG_PHY_CONTROL;
|
||||||
|
if (!of_property_read_u32(node, "qcom,otg-control", &val))
|
||||||
|
if (val == OTG_PMIC_CONTROL)
|
||||||
|
pdata->otg_control = val;
|
||||||
|
|
||||||
|
prop = of_find_property(node, "qcom,phy-init-sequence", &len);
|
||||||
|
if (!prop || !len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
words = len / sizeof(u32);
|
||||||
|
|
||||||
|
if (words >= ULPI_EXT_VENDOR_SPECIFIC) {
|
||||||
|
dev_warn(&pdev->dev, "Too big PHY init sequence %d\n", words);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdata->phy_init_seq = devm_kzalloc(&pdev->dev, len, GFP_KERNEL);
|
||||||
|
if (!pdata->phy_init_seq) {
|
||||||
|
dev_warn(&pdev->dev, "No space for PHY init sequence\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = of_property_read_u32_array(node, "qcom,phy-init-sequence",
|
||||||
|
pdata->phy_init_seq, words);
|
||||||
|
if (!ret)
|
||||||
|
pdata->phy_init_sz = words;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int msm_otg_probe(struct platform_device *pdev)
|
static int msm_otg_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct regulator_bulk_data regs[3];
|
struct regulator_bulk_data regs[3];
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
struct device_node *np = pdev->dev.of_node;
|
||||||
|
struct msm_otg_platform_data *pdata;
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
struct msm_otg *motg;
|
struct msm_otg *motg;
|
||||||
struct usb_phy *phy;
|
struct usb_phy *phy;
|
||||||
|
|
||||||
dev_info(&pdev->dev, "msm_otg probe\n");
|
|
||||||
if (!dev_get_platdata(&pdev->dev)) {
|
|
||||||
dev_err(&pdev->dev, "No platform data given. Bailing out\n");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
|
|
||||||
motg = devm_kzalloc(&pdev->dev, sizeof(struct msm_otg), GFP_KERNEL);
|
motg = devm_kzalloc(&pdev->dev, sizeof(struct msm_otg), GFP_KERNEL);
|
||||||
if (!motg) {
|
if (!motg) {
|
||||||
dev_err(&pdev->dev, "unable to allocate msm_otg\n");
|
dev_err(&pdev->dev, "unable to allocate msm_otg\n");
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pdata = dev_get_platdata(&pdev->dev);
|
||||||
|
if (!pdata) {
|
||||||
|
if (!np)
|
||||||
|
return -ENXIO;
|
||||||
|
ret = msm_otg_read_dt(pdev, motg);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
motg->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg),
|
motg->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
if (!motg->phy.otg) {
|
if (!motg->phy.otg) {
|
||||||
@ -1370,17 +1443,17 @@ static int msm_otg_probe(struct platform_device *pdev)
|
|||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
motg->pdata = dev_get_platdata(&pdev->dev);
|
|
||||||
phy = &motg->phy;
|
phy = &motg->phy;
|
||||||
phy->dev = &pdev->dev;
|
phy->dev = &pdev->dev;
|
||||||
|
|
||||||
motg->phy_reset_clk = devm_clk_get(&pdev->dev, "usb_phy_clk");
|
motg->phy_reset_clk = devm_clk_get(&pdev->dev,
|
||||||
|
np ? "phy" : "usb_phy_clk");
|
||||||
if (IS_ERR(motg->phy_reset_clk)) {
|
if (IS_ERR(motg->phy_reset_clk)) {
|
||||||
dev_err(&pdev->dev, "failed to get usb_phy_clk\n");
|
dev_err(&pdev->dev, "failed to get usb_phy_clk\n");
|
||||||
return PTR_ERR(motg->phy_reset_clk);
|
return PTR_ERR(motg->phy_reset_clk);
|
||||||
}
|
}
|
||||||
|
|
||||||
motg->clk = devm_clk_get(&pdev->dev, "usb_hs_clk");
|
motg->clk = devm_clk_get(&pdev->dev, np ? "core" : "usb_hs_clk");
|
||||||
if (IS_ERR(motg->clk)) {
|
if (IS_ERR(motg->clk)) {
|
||||||
dev_err(&pdev->dev, "failed to get usb_hs_clk\n");
|
dev_err(&pdev->dev, "failed to get usb_hs_clk\n");
|
||||||
return PTR_ERR(motg->clk);
|
return PTR_ERR(motg->clk);
|
||||||
@ -1392,7 +1465,7 @@ static int msm_otg_probe(struct platform_device *pdev)
|
|||||||
* operation and USB core cannot tolerate frequency changes on
|
* operation and USB core cannot tolerate frequency changes on
|
||||||
* CORE CLK.
|
* CORE CLK.
|
||||||
*/
|
*/
|
||||||
motg->pclk = devm_clk_get(&pdev->dev, "usb_hs_pclk");
|
motg->pclk = devm_clk_get(&pdev->dev, np ? "iface" : "usb_hs_pclk");
|
||||||
if (IS_ERR(motg->pclk)) {
|
if (IS_ERR(motg->pclk)) {
|
||||||
dev_err(&pdev->dev, "failed to get usb_hs_pclk\n");
|
dev_err(&pdev->dev, "failed to get usb_hs_pclk\n");
|
||||||
return PTR_ERR(motg->pclk);
|
return PTR_ERR(motg->pclk);
|
||||||
@ -1403,7 +1476,8 @@ static int msm_otg_probe(struct platform_device *pdev)
|
|||||||
* clock is introduced to remove the dependency on AXI
|
* clock is introduced to remove the dependency on AXI
|
||||||
* bus frequency.
|
* bus frequency.
|
||||||
*/
|
*/
|
||||||
motg->core_clk = devm_clk_get(&pdev->dev, "usb_hs_core_clk");
|
motg->core_clk = devm_clk_get(&pdev->dev,
|
||||||
|
np ? "alt_core" : "usb_hs_core_clk");
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
motg->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res));
|
motg->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res));
|
||||||
@ -1486,7 +1560,7 @@ static int msm_otg_probe(struct platform_device *pdev)
|
|||||||
device_init_wakeup(&pdev->dev, 1);
|
device_init_wakeup(&pdev->dev, 1);
|
||||||
|
|
||||||
if (motg->pdata->mode == USB_DR_MODE_OTG &&
|
if (motg->pdata->mode == USB_DR_MODE_OTG &&
|
||||||
motg->pdata->otg_control == OTG_USER_CONTROL) {
|
motg->pdata->otg_control == OTG_USER_CONTROL) {
|
||||||
ret = msm_otg_debugfs_init(motg);
|
ret = msm_otg_debugfs_init(motg);
|
||||||
if (ret)
|
if (ret)
|
||||||
dev_dbg(&pdev->dev, "Can not create mode change file\n");
|
dev_dbg(&pdev->dev, "Can not create mode change file\n");
|
||||||
@ -1639,6 +1713,7 @@ static struct platform_driver msm_otg_driver = {
|
|||||||
.name = DRIVER_NAME,
|
.name = DRIVER_NAME,
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.pm = &msm_otg_dev_pm_ops,
|
.pm = &msm_otg_dev_pm_ops,
|
||||||
|
.of_match_table = msm_otg_dt_match,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,8 +100,9 @@ enum usb_chg_type {
|
|||||||
/**
|
/**
|
||||||
* struct msm_otg_platform_data - platform device data
|
* struct msm_otg_platform_data - platform device data
|
||||||
* for msm_otg driver.
|
* for msm_otg driver.
|
||||||
* @phy_init_seq: PHY configuration sequence. val, reg pairs
|
* @phy_init_seq: PHY configuration sequence values. Value of -1 is reserved as
|
||||||
* terminated by -1.
|
* "do not overwrite default vaule at this address".
|
||||||
|
* @phy_init_sz: PHY configuration sequence size.
|
||||||
* @vbus_power: VBUS power on/off routine.
|
* @vbus_power: VBUS power on/off routine.
|
||||||
* @power_budget: VBUS power budget in mA (0 will be treated as 500mA).
|
* @power_budget: VBUS power budget in mA (0 will be treated as 500mA).
|
||||||
* @mode: Supported mode (OTG/peripheral/host).
|
* @mode: Supported mode (OTG/peripheral/host).
|
||||||
@ -109,6 +110,7 @@ enum usb_chg_type {
|
|||||||
*/
|
*/
|
||||||
struct msm_otg_platform_data {
|
struct msm_otg_platform_data {
|
||||||
int *phy_init_seq;
|
int *phy_init_seq;
|
||||||
|
int phy_init_sz;
|
||||||
void (*vbus_power)(bool on);
|
void (*vbus_power)(bool on);
|
||||||
unsigned power_budget;
|
unsigned power_budget;
|
||||||
enum usb_dr_mode mode;
|
enum usb_dr_mode mode;
|
||||||
|
Loading…
Reference in New Issue
Block a user