1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Ingenic JZ4740 "glue layer" |
4 | * |
5 | * Copyright (C) 2013, Apelete Seketeli <apelete@seketeli.net> |
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> |
13 | #include <linux/of.h> |
14 | #include <linux/phy/phy.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/usb/role.h> |
17 | #include <linux/usb/usb_phy_generic.h> |
18 | |
19 | #include "musb_core.h" |
20 | |
21 | struct jz4740_glue { |
22 | struct platform_device *pdev; |
23 | struct musb *musb; |
24 | struct clk *clk; |
25 | struct usb_role_switch *role_sw; |
26 | }; |
27 | |
28 | static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) |
29 | { |
30 | unsigned long flags; |
31 | irqreturn_t retval = IRQ_NONE, retval_dma = IRQ_NONE; |
32 | struct musb *musb = __hci; |
33 | |
34 | if (IS_ENABLED(CONFIG_USB_INVENTRA_DMA) && musb->dma_controller) |
35 | retval_dma = dma_controller_irq(irq, private_data: musb->dma_controller); |
36 | |
37 | spin_lock_irqsave(&musb->lock, flags); |
38 | |
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 | |
49 | MUSB_INTR_RESET | MUSB_INTR_SOF; |
50 | |
51 | if (musb->int_usb || musb->int_tx || musb->int_rx) |
52 | retval = musb_interrupt(musb); |
53 | |
54 | spin_unlock_irqrestore(lock: &musb->lock, flags); |
55 | |
56 | if (retval == IRQ_HANDLED || retval_dma == IRQ_HANDLED) |
57 | return IRQ_HANDLED; |
58 | |
59 | return IRQ_NONE; |
60 | } |
61 | |
62 | static struct musb_fifo_cfg jz4740_musb_fifo_cfg[] = { |
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, }, |
66 | }; |
67 | |
68 | static const struct musb_hdrc_config jz4740_musb_config = { |
69 | /* Silicon does not implement USB OTG. */ |
70 | .multipoint = 0, |
71 | /* Max EPs scanned, driver will decide which EP can be used. */ |
72 | .num_eps = 4, |
73 | /* RAMbits needed to configure EPs from table */ |
74 | .ram_bits = 9, |
75 | .fifo_cfg = jz4740_musb_fifo_cfg, |
76 | .fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg), |
77 | }; |
78 | |
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 | |
85 | if (!phy) |
86 | return 0; |
87 | |
88 | switch (role) { |
89 | case USB_ROLE_NONE: |
90 | atomic_notifier_call_chain(nh: &phy->notifier, val: USB_EVENT_NONE, v: phy); |
91 | break; |
92 | case USB_ROLE_DEVICE: |
93 | atomic_notifier_call_chain(nh: &phy->notifier, val: USB_EVENT_VBUS, v: phy); |
94 | break; |
95 | case USB_ROLE_HOST: |
96 | atomic_notifier_call_chain(nh: &phy->notifier, val: USB_EVENT_ID, v: phy); |
97 | break; |
98 | } |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static int jz4740_musb_init(struct musb *musb) |
104 | { |
105 | struct device *dev = musb->controller->parent; |
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 | }; |
112 | int err; |
113 | |
114 | glue->musb = musb; |
115 | |
116 | if (IS_ENABLED(CONFIG_GENERIC_PHY)) { |
117 | musb->phy = devm_of_phy_get_by_index(dev, np: dev->of_node, index: 0); |
118 | if (IS_ERR(ptr: musb->phy)) { |
119 | err = PTR_ERR(ptr: 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(phy: musb->phy); |
131 | if (err) { |
132 | dev_err(dev, "Failed to init PHY\n" ); |
133 | return err; |
134 | } |
135 | |
136 | err = phy_power_on(phy: 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, phandle: "phys" , index: 0); |
144 | else |
145 | musb->xceiv = devm_usb_get_phy(dev, type: USB_PHY_TYPE_USB2); |
146 | if (IS_ERR(ptr: musb->xceiv)) { |
147 | dev_err(dev, "No transceiver configured\n" ); |
148 | return PTR_ERR(ptr: musb->xceiv); |
149 | } |
150 | } |
151 | |
152 | glue->role_sw = usb_role_switch_register(parent: dev, desc: &role_sw_desc); |
153 | if (IS_ERR(ptr: glue->role_sw)) { |
154 | dev_err(dev, "Failed to register USB role switch\n" ); |
155 | err = PTR_ERR(ptr: glue->role_sw); |
156 | goto err_phy_power_down; |
157 | } |
158 | |
159 | /* |
160 | * Silicon does not implement ConfigData register. |
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; |
168 | |
169 | err_phy_power_down: |
170 | if (musb->phy) |
171 | phy_power_off(phy: musb->phy); |
172 | err_phy_shutdown: |
173 | if (musb->phy) |
174 | phy_exit(phy: musb->phy); |
175 | return err; |
176 | } |
177 | |
178 | static int jz4740_musb_exit(struct musb *musb) |
179 | { |
180 | struct jz4740_glue *glue = dev_get_drvdata(dev: musb->controller->parent); |
181 | |
182 | usb_role_switch_unregister(sw: glue->role_sw); |
183 | if (musb->phy) { |
184 | phy_power_off(phy: musb->phy); |
185 | phy_exit(phy: musb->phy); |
186 | } |
187 | |
188 | return 0; |
189 | } |
190 | |
191 | static const struct musb_platform_ops jz4740_musb_ops = { |
192 | .quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP, |
193 | .fifo_mode = 2, |
194 | .init = jz4740_musb_init, |
195 | .exit = jz4740_musb_exit, |
196 | #ifdef CONFIG_USB_INVENTRA_DMA |
197 | .dma_init = musbhs_dma_controller_create_noirq, |
198 | .dma_exit = musbhs_dma_controller_destroy, |
199 | #endif |
200 | }; |
201 | |
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 | |
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 | |
235 | static int jz4740_probe(struct platform_device *pdev) |
236 | { |
237 | struct device *dev = &pdev->dev; |
238 | const struct musb_hdrc_platform_data *pdata; |
239 | struct platform_device *musb; |
240 | struct jz4740_glue *glue; |
241 | struct clk *clk; |
242 | int ret; |
243 | |
244 | glue = devm_kzalloc(dev, size: sizeof(*glue), GFP_KERNEL); |
245 | if (!glue) |
246 | return -ENOMEM; |
247 | |
248 | pdata = of_device_get_match_data(dev); |
249 | if (!pdata) { |
250 | dev_err(dev, "missing platform data\n" ); |
251 | return -EINVAL; |
252 | } |
253 | |
254 | musb = platform_device_alloc(name: "musb-hdrc" , PLATFORM_DEVID_AUTO); |
255 | if (!musb) { |
256 | dev_err(dev, "failed to allocate musb device\n" ); |
257 | return -ENOMEM; |
258 | } |
259 | |
260 | clk = devm_clk_get(dev, id: "udc" ); |
261 | if (IS_ERR(ptr: clk)) { |
262 | dev_err(dev, "failed to get clock\n" ); |
263 | ret = PTR_ERR(ptr: clk); |
264 | goto err_platform_device_put; |
265 | } |
266 | |
267 | ret = clk_prepare_enable(clk); |
268 | if (ret) { |
269 | dev_err(dev, "failed to enable clock\n" ); |
270 | goto err_platform_device_put; |
271 | } |
272 | |
273 | musb->dev.parent = dev; |
274 | musb->dev.dma_mask = &musb->dev.coherent_dma_mask; |
275 | musb->dev.coherent_dma_mask = DMA_BIT_MASK(32); |
276 | device_set_of_node_from_dev(dev: &musb->dev, dev2: dev); |
277 | |
278 | glue->pdev = musb; |
279 | glue->clk = clk; |
280 | |
281 | platform_set_drvdata(pdev, data: glue); |
282 | |
283 | ret = platform_device_add_resources(pdev: musb, res: pdev->resource, |
284 | num: pdev->num_resources); |
285 | if (ret) { |
286 | dev_err(dev, "failed to add resources\n" ); |
287 | goto err_clk_disable; |
288 | } |
289 | |
290 | ret = platform_device_add_data(pdev: musb, data: pdata, size: sizeof(*pdata)); |
291 | if (ret) { |
292 | dev_err(dev, "failed to add platform_data\n" ); |
293 | goto err_clk_disable; |
294 | } |
295 | |
296 | ret = platform_device_add(pdev: musb); |
297 | if (ret) { |
298 | dev_err(dev, "failed to register musb device\n" ); |
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(pdev: musb); |
308 | return ret; |
309 | } |
310 | |
311 | static void jz4740_remove(struct platform_device *pdev) |
312 | { |
313 | struct jz4740_glue *glue = platform_get_drvdata(pdev); |
314 | |
315 | platform_device_unregister(glue->pdev); |
316 | clk_disable_unprepare(clk: glue->clk); |
317 | } |
318 | |
319 | static const struct of_device_id jz4740_musb_of_match[] = { |
320 | { .compatible = "ingenic,jz4740-musb" , .data = &jz4740_musb_pdata }, |
321 | { .compatible = "ingenic,jz4770-musb" , .data = &jz4770_musb_pdata }, |
322 | { /* sentinel */ }, |
323 | }; |
324 | MODULE_DEVICE_TABLE(of, jz4740_musb_of_match); |
325 | |
326 | static struct platform_driver jz4740_driver = { |
327 | .probe = jz4740_probe, |
328 | .remove_new = jz4740_remove, |
329 | .driver = { |
330 | .name = "musb-jz4740" , |
331 | .of_match_table = jz4740_musb_of_match, |
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); |
339 | |