1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * omap_cf.c -- OMAP 16xx CompactFlash controller driver |
4 | * |
5 | * Copyright (c) 2005 David Brownell |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/init.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/slab.h> |
16 | |
17 | #include <pcmcia/ss.h> |
18 | |
19 | #include <asm/io.h> |
20 | #include <linux/sizes.h> |
21 | |
22 | #include <linux/soc/ti/omap1-io.h> |
23 | #include <linux/soc/ti/omap1-soc.h> |
24 | #include <linux/soc/ti/omap1-mux.h> |
25 | |
26 | /* NOTE: don't expect this to support many I/O cards. The 16xx chips have |
27 | * hard-wired timings to support Compact Flash memory cards; they won't work |
28 | * with various other devices (like WLAN adapters) without some external |
29 | * logic to help out. |
30 | * |
31 | * NOTE: CF controller docs disagree with address space docs as to where |
32 | * CF_BASE really lives; this is a doc erratum. |
33 | */ |
34 | #define CF_BASE 0xfffe2800 |
35 | |
36 | /* status; read after IRQ */ |
37 | #define CF_STATUS (CF_BASE + 0x00) |
38 | # define CF_STATUS_BAD_READ (1 << 2) |
39 | # define CF_STATUS_BAD_WRITE (1 << 1) |
40 | # define CF_STATUS_CARD_DETECT (1 << 0) |
41 | |
42 | /* which chipselect (CS0..CS3) is used for CF (active low) */ |
43 | #define CF_CFG (CF_BASE + 0x02) |
44 | |
45 | /* card reset */ |
46 | #define CF_CONTROL (CF_BASE + 0x04) |
47 | # define CF_CONTROL_RESET (1 << 0) |
48 | |
49 | #define omap_cf_present() (!(omap_readw(CF_STATUS) & CF_STATUS_CARD_DETECT)) |
50 | |
51 | /*--------------------------------------------------------------------------*/ |
52 | |
53 | static const char driver_name[] = "omap_cf" ; |
54 | |
55 | struct omap_cf_socket { |
56 | struct pcmcia_socket socket; |
57 | |
58 | struct timer_list timer; |
59 | unsigned present:1; |
60 | unsigned active:1; |
61 | |
62 | struct platform_device *pdev; |
63 | unsigned long phys_cf; |
64 | u_int irq; |
65 | struct resource iomem; |
66 | }; |
67 | |
68 | #define POLL_INTERVAL (2 * HZ) |
69 | |
70 | /*--------------------------------------------------------------------------*/ |
71 | |
72 | static int omap_cf_ss_init(struct pcmcia_socket *s) |
73 | { |
74 | return 0; |
75 | } |
76 | |
77 | /* the timer is primarily to kick this socket's pccardd */ |
78 | static void omap_cf_timer(struct timer_list *t) |
79 | { |
80 | struct omap_cf_socket *cf = from_timer(cf, t, timer); |
81 | unsigned present = omap_cf_present(); |
82 | |
83 | if (present != cf->present) { |
84 | cf->present = present; |
85 | pr_debug("%s: card %s\n" , driver_name, |
86 | present ? "present" : "gone" ); |
87 | pcmcia_parse_events(socket: &cf->socket, SS_DETECT); |
88 | } |
89 | |
90 | if (cf->active) |
91 | mod_timer(timer: &cf->timer, expires: jiffies + POLL_INTERVAL); |
92 | } |
93 | |
94 | /* This irq handler prevents "irqNNN: nobody cared" messages as drivers |
95 | * claim the card's IRQ. It may also detect some card insertions, but |
96 | * not removals; it can't always eliminate timer irqs. |
97 | */ |
98 | static irqreturn_t omap_cf_irq(int irq, void *_cf) |
99 | { |
100 | struct omap_cf_socket *cf = (struct omap_cf_socket *)_cf; |
101 | |
102 | omap_cf_timer(t: &cf->timer); |
103 | return IRQ_HANDLED; |
104 | } |
105 | |
106 | static int omap_cf_get_status(struct pcmcia_socket *s, u_int *sp) |
107 | { |
108 | if (!sp) |
109 | return -EINVAL; |
110 | |
111 | /* NOTE CF is always 3VCARD */ |
112 | if (omap_cf_present()) { |
113 | struct omap_cf_socket *cf; |
114 | |
115 | *sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD; |
116 | cf = container_of(s, struct omap_cf_socket, socket); |
117 | s->pcmcia_irq = 0; |
118 | s->pci_irq = cf->irq; |
119 | } else |
120 | *sp = 0; |
121 | return 0; |
122 | } |
123 | |
124 | static int |
125 | omap_cf_set_socket(struct pcmcia_socket *sock, struct socket_state_t *s) |
126 | { |
127 | /* REVISIT some non-OSK boards may support power switching */ |
128 | switch (s->Vcc) { |
129 | case 0: |
130 | case 33: |
131 | break; |
132 | default: |
133 | return -EINVAL; |
134 | } |
135 | |
136 | omap_readw(CF_CONTROL); |
137 | if (s->flags & SS_RESET) |
138 | omap_writew(CF_CONTROL_RESET, CF_CONTROL); |
139 | else |
140 | omap_writew(v: 0, CF_CONTROL); |
141 | |
142 | pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n" , |
143 | driver_name, s->Vcc, s->io_irq, s->flags, s->csc_mask); |
144 | |
145 | return 0; |
146 | } |
147 | |
148 | static int omap_cf_ss_suspend(struct pcmcia_socket *s) |
149 | { |
150 | pr_debug("%s: %s\n" , driver_name, __func__); |
151 | return omap_cf_set_socket(sock: s, s: &dead_socket); |
152 | } |
153 | |
154 | /* regions are 2K each: mem, attrib, io (and reserved-for-ide) */ |
155 | |
156 | static int |
157 | omap_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) |
158 | { |
159 | struct omap_cf_socket *cf; |
160 | |
161 | cf = container_of(s, struct omap_cf_socket, socket); |
162 | io->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT; |
163 | io->start = cf->phys_cf + SZ_4K; |
164 | io->stop = io->start + SZ_2K - 1; |
165 | return 0; |
166 | } |
167 | |
168 | static int |
169 | omap_cf_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *map) |
170 | { |
171 | struct omap_cf_socket *cf; |
172 | |
173 | if (map->card_start) |
174 | return -EINVAL; |
175 | cf = container_of(s, struct omap_cf_socket, socket); |
176 | map->static_start = cf->phys_cf; |
177 | map->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT; |
178 | if (map->flags & MAP_ATTRIB) |
179 | map->static_start += SZ_2K; |
180 | return 0; |
181 | } |
182 | |
183 | static struct pccard_operations omap_cf_ops = { |
184 | .init = omap_cf_ss_init, |
185 | .suspend = omap_cf_ss_suspend, |
186 | .get_status = omap_cf_get_status, |
187 | .set_socket = omap_cf_set_socket, |
188 | .set_io_map = omap_cf_set_io_map, |
189 | .set_mem_map = omap_cf_set_mem_map, |
190 | }; |
191 | |
192 | /*--------------------------------------------------------------------------*/ |
193 | |
194 | /* |
195 | * NOTE: right now the only board-specific platform_data is |
196 | * "what chipselect is used". Boards could want more. |
197 | */ |
198 | |
199 | static int __init omap_cf_probe(struct platform_device *pdev) |
200 | { |
201 | unsigned seg; |
202 | struct omap_cf_socket *cf; |
203 | int irq; |
204 | int status; |
205 | struct resource *res; |
206 | struct resource iospace = DEFINE_RES_IO(SZ_64, SZ_4K); |
207 | |
208 | seg = (int) pdev->dev.platform_data; |
209 | if (seg == 0 || seg > 3) |
210 | return -ENODEV; |
211 | |
212 | /* either CFLASH.IREQ (INT_1610_CF) or some GPIO */ |
213 | irq = platform_get_irq(pdev, 0); |
214 | if (irq < 0) |
215 | return -EINVAL; |
216 | |
217 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
218 | |
219 | cf = kzalloc(size: sizeof *cf, GFP_KERNEL); |
220 | if (!cf) |
221 | return -ENOMEM; |
222 | timer_setup(&cf->timer, omap_cf_timer, 0); |
223 | |
224 | cf->pdev = pdev; |
225 | platform_set_drvdata(pdev, data: cf); |
226 | |
227 | /* this primarily just shuts up irq handling noise */ |
228 | status = request_irq(irq, handler: omap_cf_irq, IRQF_SHARED, |
229 | name: driver_name, dev: cf); |
230 | if (status < 0) |
231 | goto fail0; |
232 | cf->irq = irq; |
233 | cf->socket.pci_irq = irq; |
234 | cf->phys_cf = res->start; |
235 | |
236 | /* pcmcia layer only remaps "real" memory */ |
237 | cf->socket.io_offset = iospace.start; |
238 | status = pci_remap_iospace(res: &iospace, phys_addr: cf->phys_cf + SZ_4K); |
239 | if (status) { |
240 | status = -ENOMEM; |
241 | goto fail1; |
242 | } |
243 | |
244 | if (!request_mem_region(cf->phys_cf, SZ_8K, driver_name)) { |
245 | status = -ENXIO; |
246 | goto fail1; |
247 | } |
248 | |
249 | /* NOTE: CF conflicts with MMC1 */ |
250 | omap_cfg_reg(reg_cfg: W11_1610_CF_CD1); |
251 | omap_cfg_reg(reg_cfg: P11_1610_CF_CD2); |
252 | omap_cfg_reg(reg_cfg: R11_1610_CF_IOIS16); |
253 | omap_cfg_reg(reg_cfg: V10_1610_CF_IREQ); |
254 | omap_cfg_reg(reg_cfg: W10_1610_CF_RESET); |
255 | |
256 | omap_writew(v: ~(1 << seg), CF_CFG); |
257 | |
258 | pr_info("%s: cs%d on irq %d\n" , driver_name, seg, irq); |
259 | |
260 | /* CF uses armxor_ck, which is "always" available */ |
261 | |
262 | pr_debug("%s: sts %04x cfg %04x control %04x %s\n" , driver_name, |
263 | omap_readw(CF_STATUS), omap_readw(CF_CFG), |
264 | omap_readw(CF_CONTROL), |
265 | omap_cf_present() ? "present" : "(not present)" ); |
266 | |
267 | cf->socket.owner = THIS_MODULE; |
268 | cf->socket.dev.parent = &pdev->dev; |
269 | cf->socket.ops = &omap_cf_ops; |
270 | cf->socket.resource_ops = &pccard_static_ops; |
271 | cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP |
272 | | SS_CAP_MEM_ALIGN; |
273 | cf->socket.map_size = SZ_2K; |
274 | cf->socket.io[0].res = &cf->iomem; |
275 | |
276 | status = pcmcia_register_socket(socket: &cf->socket); |
277 | if (status < 0) |
278 | goto fail2; |
279 | |
280 | cf->active = 1; |
281 | mod_timer(timer: &cf->timer, expires: jiffies + POLL_INTERVAL); |
282 | return 0; |
283 | |
284 | fail2: |
285 | release_mem_region(cf->phys_cf, SZ_8K); |
286 | fail1: |
287 | free_irq(irq, cf); |
288 | fail0: |
289 | kfree(objp: cf); |
290 | return status; |
291 | } |
292 | |
293 | static int __exit omap_cf_remove(struct platform_device *pdev) |
294 | { |
295 | struct omap_cf_socket *cf = platform_get_drvdata(pdev); |
296 | |
297 | cf->active = 0; |
298 | pcmcia_unregister_socket(socket: &cf->socket); |
299 | timer_shutdown_sync(timer: &cf->timer); |
300 | release_mem_region(cf->phys_cf, SZ_8K); |
301 | free_irq(cf->irq, cf); |
302 | kfree(objp: cf); |
303 | return 0; |
304 | } |
305 | |
306 | static struct platform_driver omap_cf_driver = { |
307 | .driver = { |
308 | .name = driver_name, |
309 | }, |
310 | .remove = __exit_p(omap_cf_remove), |
311 | }; |
312 | |
313 | static int __init omap_cf_init(void) |
314 | { |
315 | if (cpu_is_omap16xx()) |
316 | return platform_driver_probe(&omap_cf_driver, omap_cf_probe); |
317 | return -ENODEV; |
318 | } |
319 | |
320 | static void __exit omap_cf_exit(void) |
321 | { |
322 | if (cpu_is_omap16xx()) |
323 | platform_driver_unregister(&omap_cf_driver); |
324 | } |
325 | |
326 | module_init(omap_cf_init); |
327 | module_exit(omap_cf_exit); |
328 | |
329 | MODULE_DESCRIPTION("OMAP CF Driver" ); |
330 | MODULE_LICENSE("GPL" ); |
331 | MODULE_ALIAS("platform:omap_cf" ); |
332 | |