1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2014 Marvell Technology Group Ltd. |
4 | * |
5 | * Antoine Tenart <antoine.tenart@free-electrons.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/dma-mapping.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/phy/phy.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/property.h> |
15 | #include <linux/usb/chipidea.h> |
16 | #include <linux/usb/hcd.h> |
17 | #include <linux/usb/ulpi.h> |
18 | |
19 | #include "ci.h" |
20 | |
21 | struct ci_hdrc_usb2_priv { |
22 | struct platform_device *ci_pdev; |
23 | struct clk *clk; |
24 | }; |
25 | |
26 | static const struct ci_hdrc_platform_data ci_default_pdata = { |
27 | .capoffset = DEF_CAPOFFSET, |
28 | .flags = CI_HDRC_DISABLE_STREAMING, |
29 | }; |
30 | |
31 | static const struct ci_hdrc_platform_data ci_zynq_pdata = { |
32 | .capoffset = DEF_CAPOFFSET, |
33 | .flags = CI_HDRC_PHY_VBUS_CONTROL, |
34 | }; |
35 | |
36 | static const struct ci_hdrc_platform_data ci_zevio_pdata = { |
37 | .capoffset = DEF_CAPOFFSET, |
38 | .flags = CI_HDRC_REGS_SHARED | CI_HDRC_FORCE_FULLSPEED, |
39 | }; |
40 | |
41 | static const struct of_device_id ci_hdrc_usb2_of_match[] = { |
42 | { .compatible = "chipidea,usb2" }, |
43 | { .compatible = "xlnx,zynq-usb-2.20a" , .data = &ci_zynq_pdata }, |
44 | { .compatible = "lsi,zevio-usb" , .data = &ci_zevio_pdata }, |
45 | { } |
46 | }; |
47 | MODULE_DEVICE_TABLE(of, ci_hdrc_usb2_of_match); |
48 | |
49 | static int ci_hdrc_usb2_probe(struct platform_device *pdev) |
50 | { |
51 | struct device *dev = &pdev->dev; |
52 | struct ci_hdrc_usb2_priv *priv; |
53 | struct ci_hdrc_platform_data *ci_pdata = dev_get_platdata(dev); |
54 | const struct ci_hdrc_platform_data *data; |
55 | int ret; |
56 | |
57 | if (!ci_pdata) { |
58 | ci_pdata = devm_kmalloc(dev, size: sizeof(*ci_pdata), GFP_KERNEL); |
59 | if (!ci_pdata) |
60 | return -ENOMEM; |
61 | *ci_pdata = ci_default_pdata; /* struct copy */ |
62 | } |
63 | |
64 | data = device_get_match_data(dev: &pdev->dev); |
65 | if (data) |
66 | /* struct copy */ |
67 | *ci_pdata = *data; |
68 | |
69 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
70 | if (!priv) |
71 | return -ENOMEM; |
72 | |
73 | priv->clk = devm_clk_get_optional(dev, NULL); |
74 | if (IS_ERR(ptr: priv->clk)) |
75 | return PTR_ERR(ptr: priv->clk); |
76 | |
77 | ret = clk_prepare_enable(clk: priv->clk); |
78 | if (ret) { |
79 | dev_err(dev, "failed to enable the clock: %d\n" , ret); |
80 | return ret; |
81 | } |
82 | |
83 | ci_pdata->name = dev_name(dev); |
84 | |
85 | priv->ci_pdev = ci_hdrc_add_device(dev, res: pdev->resource, |
86 | nres: pdev->num_resources, platdata: ci_pdata); |
87 | if (IS_ERR(ptr: priv->ci_pdev)) { |
88 | ret = PTR_ERR(ptr: priv->ci_pdev); |
89 | if (ret != -EPROBE_DEFER) |
90 | dev_err(dev, |
91 | "failed to register ci_hdrc platform device: %d\n" , |
92 | ret); |
93 | goto clk_err; |
94 | } |
95 | |
96 | platform_set_drvdata(pdev, data: priv); |
97 | |
98 | pm_runtime_no_callbacks(dev); |
99 | pm_runtime_enable(dev); |
100 | |
101 | return 0; |
102 | |
103 | clk_err: |
104 | clk_disable_unprepare(clk: priv->clk); |
105 | return ret; |
106 | } |
107 | |
108 | static void ci_hdrc_usb2_remove(struct platform_device *pdev) |
109 | { |
110 | struct ci_hdrc_usb2_priv *priv = platform_get_drvdata(pdev); |
111 | |
112 | pm_runtime_disable(dev: &pdev->dev); |
113 | ci_hdrc_remove_device(pdev: priv->ci_pdev); |
114 | clk_disable_unprepare(clk: priv->clk); |
115 | } |
116 | |
117 | static struct platform_driver ci_hdrc_usb2_driver = { |
118 | .probe = ci_hdrc_usb2_probe, |
119 | .remove_new = ci_hdrc_usb2_remove, |
120 | .driver = { |
121 | .name = "chipidea-usb2" , |
122 | .of_match_table = ci_hdrc_usb2_of_match, |
123 | }, |
124 | }; |
125 | module_platform_driver(ci_hdrc_usb2_driver); |
126 | |
127 | MODULE_DESCRIPTION("ChipIdea HDRC USB2 binding for ci13xxx" ); |
128 | MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>" ); |
129 | MODULE_LICENSE("GPL" ); |
130 | |