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
_init(void)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
_info(struct modinfo * modinfop)145 _info(struct modinfo *modinfop)
146 {
147 return (mod_info(&modlinkage, modinfop));
148 }
149
150
151 int
_fini(void)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
bbc_beep_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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
bbc_beep_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
bbc_beep_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)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
bbc_beep_freq(void * arg,int freq)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
bbc_beep_on(void * arg)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
bbc_beep_off(void * arg)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
bbc_beep_map_regs(dev_info_t * dip,bbc_beep_state_t * bbc_beeptr)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 *
bbc_beep_obtain_state(dev_info_t * dip)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
bbc_beep_cleanup(bbc_beep_state_t * bbc_beeptr)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
bbc_beep_hztocounter(int freq)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