1/* $NetBSD: pcppi.c,v 1.48 2024/02/09 18:20:00 andvar 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.48 2024/02/09 18:20:00 andvar 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
58void	pcppi_pckbd_bell(void *, u_int, u_int, u_int, int);
59#endif
60
61int	pcppi_match(device_t, cfdata_t, void *);
62void	pcppi_isa_attach(device_t, device_t, void *);
63void	pcppi_childdet(device_t, device_t);
64int	pcppi_rescan(device_t, const char *, const int *);
65
66CFATTACH_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
70static int pcppisearch(device_t, cfdata_t, const int *, void *);
71static void pcppi_bell_stop(struct pcppi_softc *);
72static void pcppi_bell_callout(void *);
73
74#if NATTIMER > 0
75static void pcppi_attach_speaker(device_t);
76static void pcppi_detach_speaker(struct pcppi_softc *);
77#endif
78
79int
80pcppi_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 too quick to produce anything 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
142lose:
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
157void
158pcppi_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
176void
177pcppi_childdet(device_t self, device_t child)
178{
179
180	/* we hold no child references, so do nothing */
181}
182
183int
184pcppi_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
215void
216pcppi_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, NULL, NULL);
237}
238
239int
240pcppi_rescan(device_t self, const char *ifattr, const int *locators)
241{
242	struct pcppi_softc *sc = device_private(self);
243        struct pcppi_attach_args pa;
244
245	pa.pa_cookie = sc;
246	pa.pa_bell_func = pcppi_bell;
247
248	config_search(sc->sc_dv, &pa,
249	    CFARGS(.search = pcppisearch));
250
251	return 0;
252}
253
254static int
255pcppisearch(device_t parent, cfdata_t cf, const int *locs, void *aux)
256{
257
258	if (config_probe(parent, cf, aux))
259		config_attach(parent, cf, aux, NULL,
260		    CFARGS(.locators = locs));
261
262	return 0;
263}
264
265#if NATTIMER > 0
266static void
267pcppi_detach_speaker(struct pcppi_softc *sc)
268{
269	if (sc->sc_timer != NULL) {
270		attimer_detach_speaker(sc->sc_timer);
271		sc->sc_timer = NULL;
272	}
273}
274
275static void
276pcppi_attach_speaker(device_t self)
277{
278	struct pcppi_softc *sc = device_private(self);
279
280	if ((sc->sc_timer = attimer_attach_speaker()) == NULL)
281		aprint_error_dev(self, "could not find any available timer\n");
282	else {
283		aprint_normal_dev(sc->sc_timer, "attached to %s\n",
284		    device_xname(self));
285	}
286}
287#endif
288
289void
290pcppi_bell(pcppi_tag_t self, int pitch, int period, int slp)
291{
292
293	mutex_spin_enter(&tty_lock);
294	pcppi_bell_locked(self, pitch, period, slp);
295	mutex_spin_exit(&tty_lock);
296}
297
298void
299pcppi_bell_locked(pcppi_tag_t self, int pitch, int period, int slp)
300{
301	struct pcppi_softc *sc = self;
302
303	if (sc->sc_bellactive) {
304		if (sc->sc_timeout) {
305			sc->sc_timeout = 0;
306			callout_stop(&sc->sc_bell_ch);
307		}
308		cv_broadcast(&sc->sc_slp);
309	}
310	if (pitch == 0 || period == 0) {
311		pcppi_bell_stop(sc);
312		sc->sc_bellpitch = 0;
313		return;
314	}
315	if (!sc->sc_bellactive || sc->sc_bellpitch != pitch) {
316#if NATTIMER > 0
317		if (sc->sc_timer != NULL)
318			attimer_set_pitch(sc->sc_timer, pitch);
319#endif
320		/* enable speaker */
321		bus_space_write_1(sc->sc_iot, sc->sc_ppi_ioh, 0,
322			bus_space_read_1(sc->sc_iot, sc->sc_ppi_ioh, 0)
323			| PIT_SPKR);
324	}
325	sc->sc_bellpitch = pitch;
326
327	sc->sc_bellactive = 1;
328	if (slp & PCPPI_BELL_POLL) {
329		delay((period * 1000000) / hz);
330		pcppi_bell_stop(sc);
331	} else {
332		sc->sc_timeout = 1;
333		callout_schedule(&sc->sc_bell_ch, period);
334		if (slp & PCPPI_BELL_SLEEP) {
335			cv_wait_sig(&sc->sc_slp, &tty_lock);
336		}
337	}
338}
339
340static void
341pcppi_bell_callout(void *arg)
342{
343	struct pcppi_softc *sc = arg;
344
345	mutex_spin_enter(&tty_lock);
346	if (sc->sc_timeout != 0) {
347		pcppi_bell_stop(sc);
348	}
349	mutex_spin_exit(&tty_lock);
350}
351
352static void
353pcppi_bell_stop(struct pcppi_softc *sc)
354{
355
356	sc->sc_timeout = 0;
357
358	/* disable bell */
359	bus_space_write_1(sc->sc_iot, sc->sc_ppi_ioh, 0,
360			  bus_space_read_1(sc->sc_iot, sc->sc_ppi_ioh, 0)
361			  & ~PIT_SPKR);
362	sc->sc_bellactive = 0;
363	cv_broadcast(&sc->sc_slp);
364}
365
366#if NPCKBD > 0
367void
368pcppi_pckbd_bell(void *arg, u_int pitch, u_int period, u_int volume,
369    int poll)
370{
371
372	/*
373	 * Comes in as ms, goes out at ticks; volume ignored.
374	 */
375	pcppi_bell_locked(arg, pitch, (period * hz) / 1000,
376	    poll ? PCPPI_BELL_POLL : 0);
377}
378#endif /* NPCKBD > 0 */
379