1 | /* $NetBSD: pcppi.c,v 1.44 2015/05/17 05:20:37 pgoyette Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 1996 Carnegie-Mellon University. |
5 | * All rights reserved. |
6 | * |
7 | * Author: Chris G. Demetriou |
8 | * |
9 | * Permission to use, copy, modify and distribute this software and |
10 | * its documentation is hereby granted, provided that both the copyright |
11 | * notice and this permission notice appear in all copies of the |
12 | * software, derivative works or modified versions, and any portions |
13 | * thereof, and that both notices appear in supporting documentation. |
14 | * |
15 | * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" |
16 | * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND |
17 | * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. |
18 | * |
19 | * Carnegie Mellon requests users of this software to return to |
20 | * |
21 | * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU |
22 | * School of Computer Science |
23 | * Carnegie Mellon University |
24 | * Pittsburgh PA 15213-3890 |
25 | * |
26 | * any improvements or extensions that they make and grant Carnegie the |
27 | * rights to redistribute these changes. |
28 | */ |
29 | |
30 | #include <sys/cdefs.h> |
31 | __KERNEL_RCSID(0, "$NetBSD: pcppi.c,v 1.44 2015/05/17 05:20:37 pgoyette Exp $" ); |
32 | |
33 | #include "attimer.h" |
34 | |
35 | #include <sys/param.h> |
36 | #include <sys/systm.h> |
37 | #include <sys/callout.h> |
38 | #include <sys/kernel.h> |
39 | #include <sys/proc.h> |
40 | #include <sys/device.h> |
41 | #include <sys/errno.h> |
42 | #include <sys/bus.h> |
43 | #include <sys/mutex.h> |
44 | #include <sys/condvar.h> |
45 | #include <sys/tty.h> |
46 | |
47 | #include <dev/ic/attimervar.h> |
48 | |
49 | #include <dev/isa/isareg.h> |
50 | #include <dev/isa/isavar.h> |
51 | #include <dev/isa/pcppireg.h> |
52 | #include <dev/isa/pcppivar.h> |
53 | |
54 | #include "pckbd.h" |
55 | #if NPCKBD > 0 |
56 | #include <dev/pckbport/pckbdvar.h> |
57 | |
58 | void pcppi_pckbd_bell(void *, u_int, u_int, u_int, int); |
59 | #endif |
60 | |
61 | int pcppi_match(device_t, cfdata_t, void *); |
62 | void pcppi_isa_attach(device_t, device_t, void *); |
63 | void pcppi_childdet(device_t, device_t); |
64 | int pcppi_rescan(device_t, const char *, const int *); |
65 | |
66 | CFATTACH_DECL3_NEW(pcppi, sizeof(struct pcppi_softc), |
67 | pcppi_match, pcppi_isa_attach, pcppi_detach, NULL, pcppi_rescan, |
68 | pcppi_childdet, DVF_DETACH_SHUTDOWN); |
69 | |
70 | static int pcppisearch(device_t, cfdata_t, const int *, void *); |
71 | static void pcppi_bell_stop(struct pcppi_softc *); |
72 | static void pcppi_bell_callout(void *); |
73 | |
74 | #if NATTIMER > 0 |
75 | static void pcppi_attach_speaker(device_t); |
76 | static void pcppi_detach_speaker(struct pcppi_softc *); |
77 | #endif |
78 | |
79 | int |
80 | pcppi_match(device_t parent, cfdata_t match, void *aux) |
81 | { |
82 | struct isa_attach_args *ia = aux; |
83 | bus_space_handle_t ppi_ioh; |
84 | int have_ppi, rv; |
85 | u_int8_t v, nv; |
86 | |
87 | if (ISA_DIRECT_CONFIG(ia)) |
88 | return (0); |
89 | |
90 | /* If values are hardwired to something that they can't be, punt. */ |
91 | if (ia->ia_nio < 1 || |
92 | (ia->ia_io[0].ir_addr != ISA_UNKNOWN_PORT && |
93 | ia->ia_io[0].ir_addr != IO_PPI)) |
94 | return (0); |
95 | |
96 | if (ia->ia_niomem > 0 && |
97 | (ia->ia_iomem[0].ir_addr != ISA_UNKNOWN_IOMEM)) |
98 | return (0); |
99 | |
100 | if (ia->ia_nirq > 0 && |
101 | (ia->ia_irq[0].ir_irq != ISA_UNKNOWN_IRQ)) |
102 | return (0); |
103 | |
104 | if (ia->ia_ndrq > 0 && |
105 | (ia->ia_drq[0].ir_drq != ISA_UNKNOWN_DRQ)) |
106 | return (0); |
107 | |
108 | rv = 0; |
109 | have_ppi = 0; |
110 | |
111 | if (bus_space_map(ia->ia_iot, IO_PPI, 1, 0, &ppi_ioh)) |
112 | goto lose; |
113 | have_ppi = 1; |
114 | |
115 | /* |
116 | * Check for existence of PPI. Realistically, this is either going to |
117 | * be here or nothing is going to be here. |
118 | * |
119 | * We don't want to have any chance of changing speaker output (which |
120 | * this test might, if it crashes in the middle, or something; |
121 | * normally it's be to quick to produce anthing audible), but |
122 | * many "combo chip" mock-PPI's don't seem to support the top bit |
123 | * of Port B as a settable bit. The bottom bit has to be settable, |
124 | * since the speaker driver hardware still uses it. |
125 | */ |
126 | v = bus_space_read_1(ia->ia_iot, ppi_ioh, 0); /* XXX */ |
127 | bus_space_write_1(ia->ia_iot, ppi_ioh, 0, v ^ 0x01); /* XXX */ |
128 | nv = bus_space_read_1(ia->ia_iot, ppi_ioh, 0); /* XXX */ |
129 | if (((nv ^ v) & 0x01) == 0x01) |
130 | rv = 1; |
131 | bus_space_write_1(ia->ia_iot, ppi_ioh, 0, v); /* XXX */ |
132 | nv = bus_space_read_1(ia->ia_iot, ppi_ioh, 0); /* XXX */ |
133 | if (((nv ^ v) & 0x01) != 0x00) { |
134 | rv = 0; |
135 | goto lose; |
136 | } |
137 | |
138 | /* |
139 | * We assume that the programmable interval timer is there. |
140 | */ |
141 | |
142 | lose: |
143 | if (have_ppi) |
144 | bus_space_unmap(ia->ia_iot, ppi_ioh, 1); |
145 | if (rv) { |
146 | ia->ia_io[0].ir_addr = IO_PPI; |
147 | ia->ia_io[0].ir_size = 1; |
148 | ia->ia_nio = 1; |
149 | |
150 | ia->ia_niomem = 0; |
151 | ia->ia_nirq = 0; |
152 | ia->ia_ndrq = 0; |
153 | } |
154 | return (rv); |
155 | } |
156 | |
157 | void |
158 | pcppi_isa_attach(device_t parent, device_t self, void *aux) |
159 | { |
160 | struct pcppi_softc *sc = device_private(self); |
161 | struct isa_attach_args *ia = aux; |
162 | bus_space_tag_t iot; |
163 | |
164 | sc->sc_dv = self; |
165 | sc->sc_iot = iot = ia->ia_iot; |
166 | |
167 | sc->sc_size = 1; |
168 | if (bus_space_map(iot, IO_PPI, sc->sc_size, 0, &sc->sc_ppi_ioh)) |
169 | panic("pcppi_attach: couldn't map" ); |
170 | |
171 | aprint_naive("\n" ); |
172 | aprint_normal("\n" ); |
173 | pcppi_attach(sc); |
174 | } |
175 | |
176 | void |
177 | pcppi_childdet(device_t self, device_t child) |
178 | { |
179 | |
180 | /* we hold no child references, so do nothing */ |
181 | } |
182 | |
183 | int |
184 | pcppi_detach(device_t self, int flags) |
185 | { |
186 | int rc; |
187 | struct pcppi_softc *sc = device_private(self); |
188 | |
189 | #if NATTIMER > 0 |
190 | pcppi_detach_speaker(sc); |
191 | #endif |
192 | |
193 | if ((rc = config_detach_children(sc->sc_dv, flags)) != 0) |
194 | return rc; |
195 | |
196 | pmf_device_deregister(self); |
197 | |
198 | #if NPCKBD > 0 |
199 | pckbd_unhook_bell(pcppi_pckbd_bell, sc); |
200 | #endif |
201 | mutex_spin_enter(&tty_lock); |
202 | pcppi_bell_stop(sc); |
203 | mutex_spin_exit(&tty_lock); |
204 | |
205 | callout_halt(&sc->sc_bell_ch, NULL); |
206 | callout_destroy(&sc->sc_bell_ch); |
207 | |
208 | cv_destroy(&sc->sc_slp); |
209 | |
210 | bus_space_unmap(sc->sc_iot, sc->sc_ppi_ioh, sc->sc_size); |
211 | |
212 | return 0; |
213 | } |
214 | |
215 | void |
216 | pcppi_attach(struct pcppi_softc *sc) |
217 | { |
218 | device_t self = sc->sc_dv; |
219 | |
220 | callout_init(&sc->sc_bell_ch, CALLOUT_MPSAFE); |
221 | callout_setfunc(&sc->sc_bell_ch, pcppi_bell_callout, sc); |
222 | cv_init(&sc->sc_slp, "bell" ); |
223 | |
224 | sc->sc_bellactive = sc->sc_bellpitch = 0; |
225 | |
226 | #if NPCKBD > 0 |
227 | /* Provide a beeper for the PC Keyboard, if there isn't one already. */ |
228 | pckbd_hookup_bell(pcppi_pckbd_bell, sc); |
229 | #endif |
230 | #if NATTIMER > 0 |
231 | config_defer(sc->sc_dv, pcppi_attach_speaker); |
232 | #endif |
233 | if (!pmf_device_register(self, NULL, NULL)) |
234 | aprint_error_dev(self, "couldn't establish power handler\n" ); |
235 | |
236 | pcppi_rescan(self, "pcppi" , NULL); |
237 | } |
238 | |
239 | int |
240 | pcppi_rescan(device_t self, const char *ifattr, const int *flags) |
241 | { |
242 | struct pcppi_softc *sc = device_private(self); |
243 | struct pcppi_attach_args pa; |
244 | |
245 | if (!ifattr_match(ifattr, "pcppi" )) |
246 | return 0; |
247 | |
248 | pa.pa_cookie = sc; |
249 | config_search_loc(pcppisearch, sc->sc_dv, "pcppi" , NULL, &pa); |
250 | |
251 | return 0; |
252 | } |
253 | |
254 | static int |
255 | pcppisearch(device_t parent, cfdata_t cf, const int *locs, void *aux) |
256 | { |
257 | |
258 | if (config_match(parent, cf, aux)) |
259 | config_attach_loc(parent, cf, locs, aux, NULL); |
260 | |
261 | return 0; |
262 | } |
263 | |
264 | #if NATTIMER > 0 |
265 | static void |
266 | pcppi_detach_speaker(struct pcppi_softc *sc) |
267 | { |
268 | if (sc->sc_timer != NULL) { |
269 | attimer_detach_speaker(sc->sc_timer); |
270 | sc->sc_timer = NULL; |
271 | } |
272 | } |
273 | |
274 | static void |
275 | pcppi_attach_speaker(device_t self) |
276 | { |
277 | struct pcppi_softc *sc = device_private(self); |
278 | |
279 | if ((sc->sc_timer = attimer_attach_speaker()) == NULL) |
280 | aprint_error_dev(self, "could not find any available timer\n" ); |
281 | else { |
282 | aprint_normal_dev(sc->sc_timer, "attached to %s\n" , |
283 | device_xname(self)); |
284 | } |
285 | } |
286 | #endif |
287 | |
288 | void |
289 | pcppi_bell(pcppi_tag_t self, int pitch, int period, int slp) |
290 | { |
291 | |
292 | mutex_spin_enter(&tty_lock); |
293 | pcppi_bell_locked(self, pitch, period, slp); |
294 | mutex_spin_exit(&tty_lock); |
295 | } |
296 | |
297 | void |
298 | pcppi_bell_locked(pcppi_tag_t self, int pitch, int period, int slp) |
299 | { |
300 | struct pcppi_softc *sc = self; |
301 | |
302 | if (sc->sc_bellactive) { |
303 | if (sc->sc_timeout) { |
304 | sc->sc_timeout = 0; |
305 | callout_stop(&sc->sc_bell_ch); |
306 | } |
307 | cv_broadcast(&sc->sc_slp); |
308 | } |
309 | if (pitch == 0 || period == 0) { |
310 | pcppi_bell_stop(sc); |
311 | sc->sc_bellpitch = 0; |
312 | return; |
313 | } |
314 | if (!sc->sc_bellactive || sc->sc_bellpitch != pitch) { |
315 | #if NATTIMER > 0 |
316 | if (sc->sc_timer != NULL) |
317 | attimer_set_pitch(sc->sc_timer, pitch); |
318 | #endif |
319 | /* enable speaker */ |
320 | bus_space_write_1(sc->sc_iot, sc->sc_ppi_ioh, 0, |
321 | bus_space_read_1(sc->sc_iot, sc->sc_ppi_ioh, 0) |
322 | | PIT_SPKR); |
323 | } |
324 | sc->sc_bellpitch = pitch; |
325 | |
326 | sc->sc_bellactive = 1; |
327 | if (slp & PCPPI_BELL_POLL) { |
328 | delay((period * 1000000) / hz); |
329 | pcppi_bell_stop(sc); |
330 | } else { |
331 | sc->sc_timeout = 1; |
332 | callout_schedule(&sc->sc_bell_ch, period); |
333 | if (slp & PCPPI_BELL_SLEEP) { |
334 | cv_wait_sig(&sc->sc_slp, &tty_lock); |
335 | } |
336 | } |
337 | } |
338 | |
339 | static void |
340 | pcppi_bell_callout(void *arg) |
341 | { |
342 | struct pcppi_softc *sc = arg; |
343 | |
344 | mutex_spin_enter(&tty_lock); |
345 | if (sc->sc_timeout != 0) { |
346 | pcppi_bell_stop(sc); |
347 | } |
348 | mutex_spin_exit(&tty_lock); |
349 | } |
350 | |
351 | static void |
352 | pcppi_bell_stop(struct pcppi_softc *sc) |
353 | { |
354 | |
355 | sc->sc_timeout = 0; |
356 | |
357 | /* disable bell */ |
358 | bus_space_write_1(sc->sc_iot, sc->sc_ppi_ioh, 0, |
359 | bus_space_read_1(sc->sc_iot, sc->sc_ppi_ioh, 0) |
360 | & ~PIT_SPKR); |
361 | sc->sc_bellactive = 0; |
362 | cv_broadcast(&sc->sc_slp); |
363 | } |
364 | |
365 | #if NPCKBD > 0 |
366 | void |
367 | pcppi_pckbd_bell(void *arg, u_int pitch, u_int period, u_int volume, |
368 | int poll) |
369 | { |
370 | |
371 | /* |
372 | * Comes in as ms, goes out at ticks; volume ignored. |
373 | */ |
374 | pcppi_bell_locked(arg, pitch, (period * hz) / 1000, |
375 | poll ? PCPPI_BELL_POLL : 0); |
376 | } |
377 | #endif /* NPCKBD > 0 */ |
378 | |