Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: GPL-2.0+ |
10434d27 AS |
2 | /* |
3 | * Ingenic JZ4740 "glue layer" | |
4 | * | |
5 | * Copyright (C) 2013, Apelete Seketeli <apelete@seketeli.net> | |
10434d27 AS |
6 | */ |
7 | ||
8 | #include <linux/clk.h> | |
9 | #include <linux/dma-mapping.h> | |
10 | #include <linux/errno.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/module.h> | |
484468fb | 13 | #include <linux/of.h> |
9cd07479 | 14 | #include <linux/phy/phy.h> |
10434d27 | 15 | #include <linux/platform_device.h> |
5004eaa2 | 16 | #include <linux/usb/role.h> |
3d75bd3d | 17 | #include <linux/usb/usb_phy_generic.h> |
10434d27 AS |
18 | |
19 | #include "musb_core.h" | |
20 | ||
21 | struct jz4740_glue { | |
90fad5d7 | 22 | struct platform_device *pdev; |
5004eaa2 | 23 | struct musb *musb; |
10434d27 | 24 | struct clk *clk; |
5004eaa2 | 25 | struct usb_role_switch *role_sw; |
10434d27 AS |
26 | }; |
27 | ||
28 | static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) | |
29 | { | |
3fc32907 | 30 | unsigned long flags; |
57aadb46 | 31 | irqreturn_t retval = IRQ_NONE, retval_dma = IRQ_NONE; |
3fc32907 | 32 | struct musb *musb = __hci; |
10434d27 | 33 | |
57aadb46 PC |
34 | if (IS_ENABLED(CONFIG_USB_INVENTRA_DMA) && musb->dma_controller) |
35 | retval_dma = dma_controller_irq(irq, musb->dma_controller); | |
36 | ||
685f5f24 PC |
37 | spin_lock_irqsave(&musb->lock, flags); |
38 | ||
10434d27 AS |
39 | musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); |
40 | musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); | |
41 | musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); | |
42 | ||
43 | /* | |
44 | * The controller is gadget only, the state of the host mode IRQ bits is | |
45 | * undefined. Mask them to make sure that the musb driver core will | |
46 | * never see them set | |
47 | */ | |
48 | musb->int_usb &= MUSB_INTR_SUSPEND | MUSB_INTR_RESUME | | |
3fc32907 | 49 | MUSB_INTR_RESET | MUSB_INTR_SOF; |
10434d27 AS |
50 | |
51 | if (musb->int_usb || musb->int_tx || musb->int_rx) | |
52 | retval = musb_interrupt(musb); | |
53 | ||
54 | spin_unlock_irqrestore(&musb->lock, flags); | |
55 | ||
57aadb46 PC |
56 | if (retval == IRQ_HANDLED || retval_dma == IRQ_HANDLED) |
57 | return IRQ_HANDLED; | |
58 | ||
59 | return IRQ_NONE; | |
10434d27 AS |
60 | } |
61 | ||
62 | static struct musb_fifo_cfg jz4740_musb_fifo_cfg[] = { | |
3fc32907 PC |
63 | { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, |
64 | { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, | |
65 | { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 64, }, | |
10434d27 AS |
66 | }; |
67 | ||
1e572aa5 | 68 | static const struct musb_hdrc_config jz4740_musb_config = { |
10434d27 | 69 | /* Silicon does not implement USB OTG. */ |
3fc32907 | 70 | .multipoint = 0, |
10434d27 | 71 | /* Max EPs scanned, driver will decide which EP can be used. */ |
3fc32907 | 72 | .num_eps = 4, |
10434d27 | 73 | /* RAMbits needed to configure EPs from table */ |
3fc32907 PC |
74 | .ram_bits = 9, |
75 | .fifo_cfg = jz4740_musb_fifo_cfg, | |
76 | .fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg), | |
10434d27 AS |
77 | }; |
78 | ||
5004eaa2 PC |
79 | static int jz4740_musb_role_switch_set(struct usb_role_switch *sw, |
80 | enum usb_role role) | |
81 | { | |
82 | struct jz4740_glue *glue = usb_role_switch_get_drvdata(sw); | |
83 | struct usb_phy *phy = glue->musb->xceiv; | |
84 | ||
9cd07479 PC |
85 | if (!phy) |
86 | return 0; | |
87 | ||
5004eaa2 PC |
88 | switch (role) { |
89 | case USB_ROLE_NONE: | |
90 | atomic_notifier_call_chain(&phy->notifier, USB_EVENT_NONE, phy); | |
91 | break; | |
92 | case USB_ROLE_DEVICE: | |
93 | atomic_notifier_call_chain(&phy->notifier, USB_EVENT_VBUS, phy); | |
94 | break; | |
95 | case USB_ROLE_HOST: | |
96 | atomic_notifier_call_chain(&phy->notifier, USB_EVENT_ID, phy); | |
97 | break; | |
98 | } | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
10434d27 AS |
103 | static int jz4740_musb_init(struct musb *musb) |
104 | { | |
afbdbd37 | 105 | struct device *dev = musb->controller->parent; |
5004eaa2 PC |
106 | struct jz4740_glue *glue = dev_get_drvdata(dev); |
107 | struct usb_role_switch_desc role_sw_desc = { | |
108 | .set = jz4740_musb_role_switch_set, | |
109 | .driver_data = glue, | |
110 | .fwnode = dev_fwnode(dev), | |
111 | }; | |
9cd07479 | 112 | int err; |
afbdbd37 | 113 | |
5004eaa2 PC |
114 | glue->musb = musb; |
115 | ||
9cd07479 PC |
116 | if (IS_ENABLED(CONFIG_GENERIC_PHY)) { |
117 | musb->phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); | |
118 | if (IS_ERR(musb->phy)) { | |
119 | err = PTR_ERR(musb->phy); | |
120 | if (err != -ENODEV) { | |
121 | dev_err(dev, "Unable to get PHY\n"); | |
122 | return err; | |
123 | } | |
124 | ||
125 | musb->phy = NULL; | |
126 | } | |
127 | } | |
128 | ||
129 | if (musb->phy) { | |
130 | err = phy_init(musb->phy); | |
131 | if (err) { | |
132 | dev_err(dev, "Failed to init PHY\n"); | |
133 | return err; | |
134 | } | |
135 | ||
136 | err = phy_power_on(musb->phy); | |
137 | if (err) { | |
138 | dev_err(dev, "Unable to power on PHY\n"); | |
139 | goto err_phy_shutdown; | |
140 | } | |
141 | } else { | |
142 | if (dev->of_node) | |
143 | musb->xceiv = devm_usb_get_phy_by_phandle(dev, "phys", 0); | |
144 | else | |
145 | musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); | |
146 | if (IS_ERR(musb->xceiv)) { | |
147 | dev_err(dev, "No transceiver configured\n"); | |
148 | return PTR_ERR(musb->xceiv); | |
149 | } | |
150 | } | |
10434d27 | 151 | |
5004eaa2 PC |
152 | glue->role_sw = usb_role_switch_register(dev, &role_sw_desc); |
153 | if (IS_ERR(glue->role_sw)) { | |
23e32a59 | 154 | dev_err(dev, "Failed to register USB role switch\n"); |
9cd07479 PC |
155 | err = PTR_ERR(glue->role_sw); |
156 | goto err_phy_power_down; | |
5004eaa2 PC |
157 | } |
158 | ||
94203e1a PC |
159 | /* |
160 | * Silicon does not implement ConfigData register. | |
10434d27 AS |
161 | * Set dyn_fifo to avoid reading EP config from hardware. |
162 | */ | |
163 | musb->dyn_fifo = true; | |
164 | ||
165 | musb->isr = jz4740_musb_interrupt; | |
166 | ||
167 | return 0; | |
9cd07479 PC |
168 | |
169 | err_phy_power_down: | |
170 | if (musb->phy) | |
171 | phy_power_off(musb->phy); | |
172 | err_phy_shutdown: | |
173 | if (musb->phy) | |
174 | phy_exit(musb->phy); | |
175 | return err; | |
10434d27 AS |
176 | } |
177 | ||
5004eaa2 PC |
178 | static int jz4740_musb_exit(struct musb *musb) |
179 | { | |
180 | struct jz4740_glue *glue = dev_get_drvdata(musb->controller->parent); | |
181 | ||
182 | usb_role_switch_unregister(glue->role_sw); | |
9cd07479 PC |
183 | if (musb->phy) { |
184 | phy_power_off(musb->phy); | |
185 | phy_exit(musb->phy); | |
186 | } | |
5004eaa2 PC |
187 | |
188 | return 0; | |
189 | } | |
190 | ||
10434d27 | 191 | static const struct musb_platform_ops jz4740_musb_ops = { |
f8e9f34f | 192 | .quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP, |
8a77f05a | 193 | .fifo_mode = 2, |
10434d27 | 194 | .init = jz4740_musb_init, |
5004eaa2 | 195 | .exit = jz4740_musb_exit, |
57aadb46 PC |
196 | #ifdef CONFIG_USB_INVENTRA_DMA |
197 | .dma_init = musbhs_dma_controller_create_noirq, | |
198 | .dma_exit = musbhs_dma_controller_destroy, | |
199 | #endif | |
10434d27 AS |
200 | }; |
201 | ||
31cecb6b PC |
202 | static const struct musb_hdrc_platform_data jz4740_musb_pdata = { |
203 | .mode = MUSB_PERIPHERAL, | |
204 | .config = &jz4740_musb_config, | |
205 | .platform_ops = &jz4740_musb_ops, | |
206 | }; | |
207 | ||
e72838d4 PC |
208 | static struct musb_fifo_cfg jz4770_musb_fifo_cfg[] = { |
209 | { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, | |
210 | { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, | |
211 | { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, | |
212 | { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, | |
213 | { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, | |
214 | { .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, | |
215 | { .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, }, | |
216 | { .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, }, | |
217 | { .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, }, | |
218 | { .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, }, | |
219 | }; | |
220 | ||
221 | static struct musb_hdrc_config jz4770_musb_config = { | |
222 | .multipoint = 1, | |
223 | .num_eps = 11, | |
224 | .ram_bits = 11, | |
225 | .fifo_cfg = jz4770_musb_fifo_cfg, | |
226 | .fifo_cfg_size = ARRAY_SIZE(jz4770_musb_fifo_cfg), | |
227 | }; | |
228 | ||
229 | static const struct musb_hdrc_platform_data jz4770_musb_pdata = { | |
230 | .mode = MUSB_PERIPHERAL, /* TODO: support OTG */ | |
231 | .config = &jz4770_musb_config, | |
232 | .platform_ops = &jz4740_musb_ops, | |
233 | }; | |
234 | ||
10434d27 AS |
235 | static int jz4740_probe(struct platform_device *pdev) |
236 | { | |
4b70331b | 237 | struct device *dev = &pdev->dev; |
c12aa5be | 238 | const struct musb_hdrc_platform_data *pdata; |
10434d27 AS |
239 | struct platform_device *musb; |
240 | struct jz4740_glue *glue; | |
3fc32907 | 241 | struct clk *clk; |
10434d27 AS |
242 | int ret; |
243 | ||
4b70331b | 244 | glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); |
10434d27 AS |
245 | if (!glue) |
246 | return -ENOMEM; | |
247 | ||
c12aa5be PC |
248 | pdata = of_device_get_match_data(dev); |
249 | if (!pdata) { | |
23e32a59 | 250 | dev_err(dev, "missing platform data\n"); |
c12aa5be PC |
251 | return -EINVAL; |
252 | } | |
253 | ||
10434d27 AS |
254 | musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); |
255 | if (!musb) { | |
23e32a59 | 256 | dev_err(dev, "failed to allocate musb device\n"); |
10434d27 AS |
257 | return -ENOMEM; |
258 | } | |
259 | ||
4b70331b | 260 | clk = devm_clk_get(dev, "udc"); |
10434d27 | 261 | if (IS_ERR(clk)) { |
23e32a59 | 262 | dev_err(dev, "failed to get clock\n"); |
10434d27 AS |
263 | ret = PTR_ERR(clk); |
264 | goto err_platform_device_put; | |
265 | } | |
266 | ||
267 | ret = clk_prepare_enable(clk); | |
268 | if (ret) { | |
23e32a59 | 269 | dev_err(dev, "failed to enable clock\n"); |
10434d27 AS |
270 | goto err_platform_device_put; |
271 | } | |
272 | ||
4b70331b | 273 | musb->dev.parent = dev; |
57aadb46 PC |
274 | musb->dev.dma_mask = &musb->dev.coherent_dma_mask; |
275 | musb->dev.coherent_dma_mask = DMA_BIT_MASK(32); | |
cf081d00 | 276 | device_set_of_node_from_dev(&musb->dev, dev); |
10434d27 | 277 | |
90fad5d7 | 278 | glue->pdev = musb; |
10434d27 AS |
279 | glue->clk = clk; |
280 | ||
10434d27 AS |
281 | platform_set_drvdata(pdev, glue); |
282 | ||
283 | ret = platform_device_add_resources(musb, pdev->resource, | |
284 | pdev->num_resources); | |
285 | if (ret) { | |
23e32a59 | 286 | dev_err(dev, "failed to add resources\n"); |
10434d27 AS |
287 | goto err_clk_disable; |
288 | } | |
289 | ||
290 | ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); | |
291 | if (ret) { | |
23e32a59 | 292 | dev_err(dev, "failed to add platform_data\n"); |
10434d27 AS |
293 | goto err_clk_disable; |
294 | } | |
295 | ||
296 | ret = platform_device_add(musb); | |
297 | if (ret) { | |
23e32a59 | 298 | dev_err(dev, "failed to register musb device\n"); |
10434d27 AS |
299 | goto err_clk_disable; |
300 | } | |
301 | ||
302 | return 0; | |
303 | ||
304 | err_clk_disable: | |
305 | clk_disable_unprepare(clk); | |
306 | err_platform_device_put: | |
307 | platform_device_put(musb); | |
308 | return ret; | |
309 | } | |
310 | ||
c5477ce3 | 311 | static void jz4740_remove(struct platform_device *pdev) |
10434d27 | 312 | { |
3fc32907 | 313 | struct jz4740_glue *glue = platform_get_drvdata(pdev); |
10434d27 | 314 | |
90fad5d7 | 315 | platform_device_unregister(glue->pdev); |
10434d27 | 316 | clk_disable_unprepare(glue->clk); |
10434d27 AS |
317 | } |
318 | ||
e2d5e094 | 319 | static const struct of_device_id jz4740_musb_of_match[] = { |
c12aa5be | 320 | { .compatible = "ingenic,jz4740-musb", .data = &jz4740_musb_pdata }, |
e72838d4 | 321 | { .compatible = "ingenic,jz4770-musb", .data = &jz4770_musb_pdata }, |
94203e1a | 322 | { /* sentinel */ }, |
e2d5e094 PC |
323 | }; |
324 | MODULE_DEVICE_TABLE(of, jz4740_musb_of_match); | |
e2d5e094 | 325 | |
10434d27 AS |
326 | static struct platform_driver jz4740_driver = { |
327 | .probe = jz4740_probe, | |
c5477ce3 | 328 | .remove_new = jz4740_remove, |
10434d27 AS |
329 | .driver = { |
330 | .name = "musb-jz4740", | |
c12aa5be | 331 | .of_match_table = jz4740_musb_of_match, |
10434d27 AS |
332 | }, |
333 | }; | |
334 | ||
335 | MODULE_DESCRIPTION("JZ4740 MUSB Glue Layer"); | |
336 | MODULE_AUTHOR("Apelete Seketeli <apelete@seketeli.net>"); | |
337 | MODULE_LICENSE("GPL v2"); | |
338 | module_platform_driver(jz4740_driver); |