Commit | Line | Data |
---|---|---|
10434d27 AS |
1 | /* |
2 | * Ingenic JZ4740 "glue layer" | |
3 | * | |
4 | * Copyright (C) 2013, Apelete Seketeli <apelete@seketeli.net> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * You should have received a copy of the GNU General Public License along | |
12 | * with this program; if not, write to the Free Software Foundation, Inc., | |
13 | * 675 Mass Ave, Cambridge, MA 02139, USA. | |
14 | */ | |
15 | ||
16 | #include <linux/clk.h> | |
17 | #include <linux/dma-mapping.h> | |
18 | #include <linux/errno.h> | |
19 | #include <linux/kernel.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/platform_device.h> | |
3d75bd3d | 22 | #include <linux/usb/usb_phy_generic.h> |
10434d27 AS |
23 | |
24 | #include "musb_core.h" | |
25 | ||
26 | struct jz4740_glue { | |
27 | struct device *dev; | |
28 | struct platform_device *musb; | |
29 | struct clk *clk; | |
30 | }; | |
31 | ||
32 | static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) | |
33 | { | |
34 | unsigned long flags; | |
35 | irqreturn_t retval = IRQ_NONE; | |
36 | struct musb *musb = __hci; | |
37 | ||
38 | spin_lock_irqsave(&musb->lock, flags); | |
39 | ||
40 | musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); | |
41 | musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); | |
42 | musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); | |
43 | ||
44 | /* | |
45 | * The controller is gadget only, the state of the host mode IRQ bits is | |
46 | * undefined. Mask them to make sure that the musb driver core will | |
47 | * never see them set | |
48 | */ | |
49 | musb->int_usb &= MUSB_INTR_SUSPEND | MUSB_INTR_RESUME | | |
50 | MUSB_INTR_RESET | MUSB_INTR_SOF; | |
51 | ||
52 | if (musb->int_usb || musb->int_tx || musb->int_rx) | |
53 | retval = musb_interrupt(musb); | |
54 | ||
55 | spin_unlock_irqrestore(&musb->lock, flags); | |
56 | ||
57 | return retval; | |
58 | } | |
59 | ||
60 | static struct musb_fifo_cfg jz4740_musb_fifo_cfg[] = { | |
61 | { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, | |
62 | { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, | |
63 | { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 64, }, | |
64 | }; | |
65 | ||
1e572aa5 | 66 | static const struct musb_hdrc_config jz4740_musb_config = { |
10434d27 AS |
67 | /* Silicon does not implement USB OTG. */ |
68 | .multipoint = 0, | |
69 | /* Max EPs scanned, driver will decide which EP can be used. */ | |
70 | .num_eps = 4, | |
71 | /* RAMbits needed to configure EPs from table */ | |
72 | .ram_bits = 9, | |
73 | .fifo_cfg = jz4740_musb_fifo_cfg, | |
74 | .fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg), | |
75 | }; | |
76 | ||
77 | static struct musb_hdrc_platform_data jz4740_musb_platform_data = { | |
78 | .mode = MUSB_PERIPHERAL, | |
79 | .config = &jz4740_musb_config, | |
80 | }; | |
81 | ||
82 | static int jz4740_musb_init(struct musb *musb) | |
83 | { | |
3d75bd3d | 84 | usb_phy_generic_register(); |
10434d27 | 85 | musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); |
97b9b7dc | 86 | if (IS_ERR(musb->xceiv)) { |
10434d27 | 87 | pr_err("HS UDC: no transceiver configured\n"); |
97b9b7dc | 88 | return PTR_ERR(musb->xceiv); |
10434d27 AS |
89 | } |
90 | ||
91 | /* Silicon does not implement ConfigData register. | |
92 | * Set dyn_fifo to avoid reading EP config from hardware. | |
93 | */ | |
94 | musb->dyn_fifo = true; | |
95 | ||
96 | musb->isr = jz4740_musb_interrupt; | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
101 | static int jz4740_musb_exit(struct musb *musb) | |
102 | { | |
103 | usb_put_phy(musb->xceiv); | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
7f6283ed TL |
108 | /* |
109 | * DMA has not been confirmed to work with CONFIG_USB_INVENTRA_DMA, | |
110 | * so let's not set up the dma function pointers yet. | |
111 | */ | |
10434d27 | 112 | static const struct musb_platform_ops jz4740_musb_ops = { |
f8e9f34f | 113 | .quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP, |
8a77f05a | 114 | .fifo_mode = 2, |
10434d27 AS |
115 | .init = jz4740_musb_init, |
116 | .exit = jz4740_musb_exit, | |
117 | }; | |
118 | ||
119 | static int jz4740_probe(struct platform_device *pdev) | |
120 | { | |
121 | struct musb_hdrc_platform_data *pdata = &jz4740_musb_platform_data; | |
122 | struct platform_device *musb; | |
123 | struct jz4740_glue *glue; | |
124 | struct clk *clk; | |
125 | int ret; | |
126 | ||
127 | glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); | |
128 | if (!glue) | |
129 | return -ENOMEM; | |
130 | ||
131 | musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); | |
132 | if (!musb) { | |
133 | dev_err(&pdev->dev, "failed to allocate musb device\n"); | |
134 | return -ENOMEM; | |
135 | } | |
136 | ||
137 | clk = devm_clk_get(&pdev->dev, "udc"); | |
138 | if (IS_ERR(clk)) { | |
139 | dev_err(&pdev->dev, "failed to get clock\n"); | |
140 | ret = PTR_ERR(clk); | |
141 | goto err_platform_device_put; | |
142 | } | |
143 | ||
144 | ret = clk_prepare_enable(clk); | |
145 | if (ret) { | |
146 | dev_err(&pdev->dev, "failed to enable clock\n"); | |
147 | goto err_platform_device_put; | |
148 | } | |
149 | ||
150 | musb->dev.parent = &pdev->dev; | |
151 | ||
152 | glue->dev = &pdev->dev; | |
153 | glue->musb = musb; | |
154 | glue->clk = clk; | |
155 | ||
156 | pdata->platform_ops = &jz4740_musb_ops; | |
157 | ||
158 | platform_set_drvdata(pdev, glue); | |
159 | ||
160 | ret = platform_device_add_resources(musb, pdev->resource, | |
161 | pdev->num_resources); | |
162 | if (ret) { | |
163 | dev_err(&pdev->dev, "failed to add resources\n"); | |
164 | goto err_clk_disable; | |
165 | } | |
166 | ||
167 | ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); | |
168 | if (ret) { | |
169 | dev_err(&pdev->dev, "failed to add platform_data\n"); | |
170 | goto err_clk_disable; | |
171 | } | |
172 | ||
173 | ret = platform_device_add(musb); | |
174 | if (ret) { | |
175 | dev_err(&pdev->dev, "failed to register musb device\n"); | |
176 | goto err_clk_disable; | |
177 | } | |
178 | ||
179 | return 0; | |
180 | ||
181 | err_clk_disable: | |
182 | clk_disable_unprepare(clk); | |
183 | err_platform_device_put: | |
184 | platform_device_put(musb); | |
185 | return ret; | |
186 | } | |
187 | ||
188 | static int jz4740_remove(struct platform_device *pdev) | |
189 | { | |
190 | struct jz4740_glue *glue = platform_get_drvdata(pdev); | |
191 | ||
192 | platform_device_unregister(glue->musb); | |
3d75bd3d | 193 | usb_phy_generic_unregister(pdev); |
10434d27 AS |
194 | clk_disable_unprepare(glue->clk); |
195 | ||
196 | return 0; | |
197 | } | |
198 | ||
199 | static struct platform_driver jz4740_driver = { | |
200 | .probe = jz4740_probe, | |
201 | .remove = jz4740_remove, | |
202 | .driver = { | |
203 | .name = "musb-jz4740", | |
204 | }, | |
205 | }; | |
206 | ||
207 | MODULE_DESCRIPTION("JZ4740 MUSB Glue Layer"); | |
208 | MODULE_AUTHOR("Apelete Seketeli <apelete@seketeli.net>"); | |
209 | MODULE_LICENSE("GPL v2"); | |
210 | module_platform_driver(jz4740_driver); |