xref: /illumos-gate/usr/src/uts/sun4u/io/bbc_beep.c (revision 50981ffc7e4c5048d14890df805afee6ec113991)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 
27 /*
28  * This is the Beep driver for bbc based beep mechanism.
29  *
30  */
31 #include <sys/types.h>
32 #include <sys/conf.h>
33 #include <sys/ddi.h>
34 #include <sys/sunddi.h>
35 #include <sys/modctl.h>
36 #include <sys/ddi_impldefs.h>
37 #include <sys/kmem.h>
38 #include <sys/devops.h>
39 #include <sys/bbc_beep.h>
40 #include <sys/beep.h>
41 
42 
43 /* Pointer to the state structure */
44 static void *bbc_beep_statep;
45 
46 
47 /*
48  * Debug stuff
49  */
50 #ifdef DEBUG
51 int bbc_beep_debug = 0;
52 #define	BBC_BEEP_DEBUG(args)  if (bbc_beep_debug) cmn_err args
53 #define	BBC_BEEP_DEBUG1(args)  if (bbc_beep_debug > 1) cmn_err args
54 #else
55 #define	BBC_BEEP_DEBUG(args)
56 #define	BBC_BEEP_DEBUG1(args)
57 #endif
58 
59 
60 /*
61  * Prototypes
62  */
63 static int bbc_beep_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
64 static int bbc_beep_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
65 static int bbc_beep_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
66 		void **result);
67 static void bbc_beep_freq(void *arg, int freq);
68 static void bbc_beep_on(void *arg);
69 static void bbc_beep_off(void *arg);
70 static void bbc_beep_cleanup(bbc_beep_state_t *);
71 static int bbc_beep_map_regs(dev_info_t *, bbc_beep_state_t *);
72 static bbc_beep_state_t *bbc_beep_obtain_state(dev_info_t *);
73 static unsigned long bbc_beep_hztocounter(int);
74 
75 
76 struct cb_ops bbc_beep_cb_ops = {
77 	nulldev,	/* open  */
78 	nulldev,	/* close */
79 	nulldev,	/* strategy */
80 	nulldev,	/* print */
81 	nulldev,	/* dump */
82 	nulldev,	/* read */
83 	nulldev,	/* write */
84 	nulldev,	/* ioctl */
85 	nulldev,	/* devmap */
86 	nulldev,	/* mmap */
87 	nulldev,	/* segmap */
88 	nochpoll,	/* poll */
89 	ddi_prop_op,	/* cb_prop_op */
90 	NULL,		/* streamtab  */
91 	D_64BIT | D_MP | D_NEW| D_HOTPLUG
92 };
93 
94 
95 static struct dev_ops bbc_beep_ops = {
96 	DEVO_REV,		/* Devo_rev */
97 	0,			/* Refcnt */
98 	bbc_beep_info,		/* Info */
99 	nulldev,		/* Identify */
100 	nulldev,		/* Probe */
101 	bbc_beep_attach,	/* Attach */
102 	bbc_beep_detach,	/* Detach */
103 	nodev,			/* Reset */
104 	&bbc_beep_cb_ops,	/* Driver operations */
105 	0,			/* Bus operations */
106 	ddi_power,		/* Power */
107 	ddi_quiesce_not_supported,	/* devo_quiesce */
108 };
109 
110 
111 static struct modldrv modldrv = {
112 	&mod_driverops, 		/* This one is a driver */
113 	"BBC Beep Driver", 		/* Name of the module. */
114 	&bbc_beep_ops,			/* Driver ops */
115 };
116 
117 
118 static struct modlinkage modlinkage = {
119 	MODREV_1, (void *)&modldrv, NULL
120 };
121 
122 
123 int
124 _init(void)
125 {
126 	int error;
127 
128 	/* Initialize the soft state structures */
129 	if ((error = ddi_soft_state_init(&bbc_beep_statep,
130 	    sizeof (bbc_beep_state_t), 1)) != 0) {
131 
132 		return (error);
133 	}
134 
135 	/* Install the loadable module */
136 	if ((error = mod_install(&modlinkage)) != 0) {
137 		ddi_soft_state_fini(&bbc_beep_statep);
138 	}
139 
140 	return (error);
141 }
142 
143 
144 int
145 _info(struct modinfo *modinfop)
146 {
147 	return (mod_info(&modlinkage, modinfop));
148 }
149 
150 
151 int
152 _fini(void)
153 {
154 	int error;
155 
156 	error = mod_remove(&modlinkage);
157 
158 	if (error == 0) {
159 		/* Release per module resources */
160 		ddi_soft_state_fini(&bbc_beep_statep);
161 	}
162 
163 	return (error);
164 }
165 
166 
167 /*
168  * Beep entry points
169  */
170 
171 /*
172  * bbc_beep_attach:
173  */
174 static int
175 bbc_beep_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
176 {
177 	int		instance;		/* Instance number */
178 
179 	/* Pointer to soft state */
180 	bbc_beep_state_t	*bbc_beeptr = NULL;
181 
182 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_attach: Start"));
183 
184 	switch (cmd) {
185 		case DDI_ATTACH:
186 			break;
187 		case DDI_RESUME:
188 			return (DDI_SUCCESS);
189 		default:
190 			return (DDI_FAILURE);
191 	}
192 
193 	/* Get the instance and create soft state */
194 	instance = ddi_get_instance(dip);
195 
196 	if (ddi_soft_state_zalloc(bbc_beep_statep, instance) != 0) {
197 
198 		return (DDI_FAILURE);
199 	}
200 
201 	bbc_beeptr = ddi_get_soft_state(bbc_beep_statep, instance);
202 
203 	if (bbc_beeptr == NULL) {
204 
205 		return (DDI_FAILURE);
206 	}
207 
208 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beeptr = 0x%p, instance %x",
209 	    (void *)bbc_beeptr, instance));
210 
211 	/* Save the dip */
212 	bbc_beeptr->bbc_beep_dip = dip;
213 
214 	/* Initialize beeper mode */
215 	bbc_beeptr->bbc_beep_mode = BBC_BEEP_OFF;
216 
217 	/* Map the Beep Control and Beep counter Registers */
218 	if (bbc_beep_map_regs(dip, bbc_beeptr) != DDI_SUCCESS) {
219 
220 		BBC_BEEP_DEBUG((CE_WARN, \
221 		    "bbc_beep_attach: Mapping of bbc registers failed."));
222 
223 		bbc_beep_cleanup(bbc_beeptr);
224 
225 		return (DDI_FAILURE);
226 	}
227 
228 	(void) beep_init((void *)dip, bbc_beep_on, bbc_beep_off, bbc_beep_freq);
229 
230 	/* Display information in the banner */
231 	ddi_report_dev(dip);
232 
233 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_attach: dip = 0x%p done",
234 	    (void *)dip));
235 
236 	return (DDI_SUCCESS);
237 }
238 
239 
240 /*
241  * bbc_beep_detach:
242  */
243 static int
244 bbc_beep_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
245 {
246 	/* Pointer to soft state */
247 	bbc_beep_state_t	*bbc_beeptr = NULL;
248 
249 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_detach: Start"));
250 
251 	switch (cmd) {
252 		case DDI_SUSPEND:
253 			bbc_beeptr = bbc_beep_obtain_state(dip);
254 
255 			if (bbc_beeptr == NULL) {
256 
257 				return (DDI_FAILURE);
258 			}
259 
260 			/*
261 			 * If a beep is in progress; fail suspend
262 			 */
263 			if (bbc_beeptr->bbc_beep_mode == BBC_BEEP_OFF) {
264 				return (DDI_SUCCESS);
265 			} else {
266 				return (DDI_FAILURE);
267 			}
268 		default:
269 
270 			return (DDI_FAILURE);
271 	}
272 }
273 
274 
275 /*
276  * bbc_beep_info:
277  */
278 /* ARGSUSED */
279 static int
280 bbc_beep_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
281 		void *arg, void **result)
282 {
283 	dev_t dev;
284 	bbc_beep_state_t  *bbc_beeptr;
285 	int instance, error;
286 
287 	switch (infocmd) {
288 
289 	case DDI_INFO_DEVT2DEVINFO:
290 		dev = (dev_t)arg;
291 		instance = BEEP_UNIT(dev);
292 
293 		if ((bbc_beeptr = ddi_get_soft_state(bbc_beep_statep,
294 		    instance)) == NULL) {
295 
296 			return (DDI_FAILURE);
297 		}
298 
299 		*result = (void *)bbc_beeptr->bbc_beep_dip;
300 
301 		error = DDI_SUCCESS;
302 		break;
303 	case DDI_INFO_DEVT2INSTANCE:
304 		dev = (dev_t)arg;
305 		instance = BEEP_UNIT(dev);
306 
307 		*result = (void *)(uintptr_t)instance;
308 
309 		error = DDI_SUCCESS;
310 		break;
311 	default:
312 		error = DDI_FAILURE;
313 
314 	}
315 
316 	return (error);
317 }
318 
319 
320 /*
321  * bbc_beep_freq() :
322  *	Set the frequency
323  */
324 static void
325 bbc_beep_freq(void *arg, int freq)
326 {
327 	dev_info_t *dip = (dev_info_t *)arg;
328 	unsigned long counter;
329 	int8_t beep_c2 = 0;
330 	int8_t beep_c3 = 0;
331 
332 	bbc_beep_state_t *bbc_beeptr = bbc_beep_obtain_state(dip);
333 
334 	/* Convert the frequency in hz to the bbc counter value */
335 	counter = bbc_beep_hztocounter(freq);
336 
337 	/* Extract relevant second and third byte of counter value */
338 	beep_c2 = (counter & 0xff00) >> 8;
339 	beep_c3 = (counter & 0xff0000) >> 16;
340 
341 	/*
342 	 * We need to write individual bytes instead of writing
343 	 * all of 32 bits to take care of allignment problem.
344 	 * Write 0 to LS 8 bits and MS 8 bits
345 	 * Write beep_c3 to bit 8..15 and beep_c2 to bit 16..24
346 	 * Little Endian format
347 	 */
348 	BEEP_WRITE_COUNTER_REG(0, 0);
349 	BEEP_WRITE_COUNTER_REG(1, beep_c3);
350 	BEEP_WRITE_COUNTER_REG(2, beep_c2);
351 	BEEP_WRITE_COUNTER_REG(3, 0);
352 
353 	BBC_BEEP_DEBUG1((CE_CONT,
354 	    "bbc_beep_freq: dip = 0x%p, freq = %d, counter = 0x%x : Done",
355 	    (void *)dip, freq, (int)counter));
356 }
357 
358 
359 /*
360  * bbc_beep_on() :
361  *	Turn the beeper on
362  */
363 static void
364 bbc_beep_on(void *arg)
365 {
366 	dev_info_t *dip = (dev_info_t *)arg;
367 	bbc_beep_state_t *bbc_beeptr = bbc_beep_obtain_state(dip);
368 
369 	BEEP_WRITE_CTRL_REG(BBC_BEEP_ON);
370 
371 	bbc_beeptr->bbc_beep_mode = BBC_BEEP_ON;
372 
373 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_on: dip = 0x%p done",
374 	    (void *)dip));
375 }
376 
377 
378 /*
379  * bbc_beep_off() :
380  * 	Turn the beeper off
381  */
382 static void
383 bbc_beep_off(void *arg)
384 {
385 	dev_info_t *dip = (dev_info_t *)arg;
386 	bbc_beep_state_t *bbc_beeptr = bbc_beep_obtain_state(dip);
387 
388 	BEEP_WRITE_CTRL_REG(BBC_BEEP_OFF);
389 
390 	bbc_beeptr->bbc_beep_mode = BBC_BEEP_OFF;
391 
392 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_off: dip = 0x%p done",
393 	    (void *)dip));
394 }
395 
396 
397 /*
398  * bbc_beep_map_regs() :
399  *
400  *	The Keyboard Beep Control Register and Keyboard Beep Counter Register
401  *	should be mapped into a non-cacheable portion of the  system
402  *	addressable space.
403  */
404 static int
405 bbc_beep_map_regs(dev_info_t *dip, bbc_beep_state_t *bbc_beeptr)
406 {
407 	ddi_device_acc_attr_t attr;
408 
409 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_map_regs: Start\n"));
410 
411 	/* The host controller will be little endian */
412 	attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
413 	attr.devacc_attr_endian_flags  = DDI_STRUCTURE_LE_ACC;
414 	attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
415 
416 	/* Map in operational registers */
417 	if (ddi_regs_map_setup(dip, 0,
418 	    (caddr_t *)&bbc_beeptr->bbc_beep_regsp,
419 	    0,
420 	    sizeof (bbc_beep_regs_t),
421 	    &attr,
422 	    &bbc_beeptr->bbc_beep_regs_handle) != DDI_SUCCESS) {
423 
424 		return (DDI_FAILURE);
425 	}
426 
427 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_map_regs: done\n"));
428 
429 	return (DDI_SUCCESS);
430 }
431 
432 
433 /*
434  * bbc_beep_obtain_state:
435  */
436 static bbc_beep_state_t *
437 bbc_beep_obtain_state(dev_info_t *dip)
438 {
439 	int instance = ddi_get_instance(dip);
440 
441 	bbc_beep_state_t *state = ddi_get_soft_state(bbc_beep_statep, instance);
442 
443 	ASSERT(state != NULL);
444 
445 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_obtain_state: done"));
446 
447 	return (state);
448 }
449 
450 
451 /*
452  * bbc_beep_cleanup :
453  *	Cleanup soft state
454  */
455 static void
456 bbc_beep_cleanup(bbc_beep_state_t *bbc_beeptr)
457 {
458 	int instance = ddi_get_instance(bbc_beeptr->bbc_beep_dip);
459 
460 	ddi_soft_state_free(bbc_beep_statep, instance);
461 
462 	BBC_BEEP_DEBUG1((CE_CONT, "bbc_beep_cleanup: done"));
463 }
464 
465 
466 /*
467  * bbc_beep_hztocounter() :
468  *	Given a frequency in hz, find out the value to
469  *	be set in the Keyboard Beep Counter register
470  *	BBC beeper uses the following formula to calculate
471  * 	frequency. The formulae is :
472  *	frequency generated = system freq /2^(n+2)
473  *	Where n = position of the bit of counter register
474  *	that is turned on and can range between 10 to 18.
475  *	So in this function, the inputs are frequency generated
476  *	and system frequency and we need to find out n, i.e, which
477  *	bit to turn on.(Ref. to Section 4.2.22 of the BBC programming
478  *	manual).
479  */
480 unsigned long
481 bbc_beep_hztocounter(int freq)
482 {
483 	int 		i;
484 	unsigned long	counter;
485 	int 		newfreq, oldfreq;
486 
487 	int		system_freq;
488 
489 	/*
490 	 * Get system frequency for the root dev_info properties
491 	 */
492 	system_freq = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_root_node(),
493 	    0, "clock-frequency", 0);
494 
495 	oldfreq = 0;
496 
497 	/*
498 	 * Calculate frequency by turning on ith bit and
499 	 * matching it with the passed frequency and we do this
500 	 * in a loop for all the relevant bits
501 	 */
502 	for (i = BBC_BEEP_MIN_SHIFT, counter = 1 << BBC_BEEP_MSBIT;
503 	    i >= BBC_BEEP_MAX_SHIFT; i--, counter >>= 1) {
504 
505 		/*
506 		 * Calculate the frequency by dividing the system
507 		 * frequency by 2^i
508 		 */
509 		newfreq = system_freq >> i;
510 
511 		/*
512 		 * Check if we turn on the ith bit, the
513 		 * frequency matches exactly or not
514 		 */
515 		if (newfreq == freq) {
516 			/*
517 			 * Exact match of passed frequency with the
518 			 * counter value
519 			 */
520 
521 			return (counter);
522 		}
523 
524 		/*
525 		 * If calculated frequency is bigger
526 		 * return the passed frequency
527 		 */
528 		if (newfreq > freq) {
529 
530 			if (i == BBC_BEEP_MIN_SHIFT) {
531 				/* Input freq is less than the possible min */
532 
533 				return (counter);
534 			}
535 
536 			/*
537 			 * Find out the nearest frequency to the passed
538 			 * frequency by comparing the difference between
539 			 * the calculated frequency and the passed frequency
540 			 */
541 			if ((freq - oldfreq) > (newfreq - freq)) {
542 				/* Return new counter corres. to newfreq */
543 
544 				return (counter);
545 			}
546 
547 			/* Return old counter corresponding to oldfreq */
548 
549 			return (counter << 1);
550 		}
551 
552 		oldfreq = newfreq;
553 	}
554 
555 	/*
556 	 * Input freq is greater than the possible max;
557 	 * Back off the counter value and return max counter
558 	 * value possible in the register
559 	 */
560 	return (counter << 1);
561 }
562