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 */
44static void *bbc_beep_statep;
45
46
47/*
48 * Debug stuff
49 */
50#ifdef DEBUG
51int 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 */
63static int bbc_beep_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
64static int bbc_beep_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
65static int bbc_beep_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
66		void **result);
67static void bbc_beep_freq(void *arg, int freq);
68static void bbc_beep_on(void *arg);
69static void bbc_beep_off(void *arg);
70static void bbc_beep_cleanup(bbc_beep_state_t *);
71static int bbc_beep_map_regs(dev_info_t *, bbc_beep_state_t *);
72static bbc_beep_state_t *bbc_beep_obtain_state(dev_info_t *);
73static unsigned long bbc_beep_hztocounter(int);
74
75
76struct 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
95static 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
111static 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
118static struct modlinkage modlinkage = {
119	MODREV_1, (void *)&modldrv, NULL
120};
121
122
123int
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
144int
145_info(struct modinfo *modinfop)
146{
147	return (mod_info(&modlinkage, modinfop));
148}
149
150
151int
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 */
174static int
175bbc_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 */
243static int
244bbc_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 */
279static int
280bbc_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 */
324static void
325bbc_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 */
363static void
364bbc_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 */
382static void
383bbc_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 */
404static int
405bbc_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 */
436static bbc_beep_state_t *
437bbc_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 */
455static void
456bbc_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 */
480unsigned long
481bbc_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