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 SMBUS based beep mechanism.
29 * The driver exports the interfaces to set frequency,
30 * turn on beeper and turn off beeper to the generic beep
31 * module. If a beep is in progress, the driver discards a
32 * second beep. This driver uses the 8254 timer to program
33 * the beeper ports.
34 */
35#include <sys/types.h>
36#include <sys/conf.h>
37#include <sys/ddi.h>
38#include <sys/sunddi.h>
39#include <sys/modctl.h>
40#include <sys/ddi_impldefs.h>
41#include <sys/kmem.h>
42#include <sys/devops.h>
43#include <sys/grbeep.h>
44#include <sys/beep.h>
45
46
47/* Pointer to the state structure */
48static void *grbeep_statep;
49
50
51/*
52 * Debug stuff
53 */
54#ifdef DEBUG
55int grbeep_debug = 0;
56#define	GRBEEP_DEBUG(args)  if (grbeep_debug) cmn_err args
57#define	GRBEEP_DEBUG1(args)  if (grbeep_debug > 1) cmn_err args
58#else
59#define	GRBEEP_DEBUG(args)
60#define	GRBEEP_DEBUG1(args)
61#endif
62
63
64/*
65 * Prototypes
66 */
67static int grbeep_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
68static int grbeep_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
69static int grbeep_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
70		void **result);
71static void grbeep_freq(void *arg, int freq);
72static void grbeep_on(void *arg);
73static void grbeep_off(void *arg);
74static void grbeep_cleanup(grbeep_state_t *);
75static int grbeep_map_regs(dev_info_t *, grbeep_state_t *);
76static grbeep_state_t *grbeep_obtain_state(dev_info_t *);
77
78
79struct cb_ops grbeep_cb_ops = {
80	nulldev,	/* open  */
81	nulldev,	/* close */
82	nulldev,	/* strategy */
83	nulldev,	/* print */
84	nulldev,	/* dump */
85	nulldev,	/* read */
86	nulldev,	/* write */
87	nulldev,	/* ioctl */
88	nulldev,	/* devmap */
89	nulldev,	/* mmap */
90	nulldev,	/* segmap */
91	nochpoll,	/* poll */
92	ddi_prop_op,	/* cb_prop_op */
93	NULL,		/* streamtab  */
94	D_MP | D_NEW
95};
96
97
98static struct dev_ops grbeep_ops = {
99	DEVO_REV,		/* Devo_rev */
100	0,			/* Refcnt */
101	grbeep_info,		/* Info */
102	nulldev,		/* Identify */
103	nulldev,		/* Probe */
104	grbeep_attach,		/* Attach */
105	grbeep_detach,		/* Detach */
106	nodev,			/* Reset */
107	&grbeep_cb_ops,		/* Driver operations */
108	0,			/* Bus operations */
109	NULL,			/* Power */
110	ddi_quiesce_not_supported,	/* devo_quiesce */
111};
112
113
114static struct modldrv modldrv = {
115	&mod_driverops, 		/* This one is a driver */
116	"SMBUS Beep Driver", 		/* Name of the module. */
117	&grbeep_ops,			/* Driver ops */
118};
119
120
121static struct modlinkage modlinkage = {
122	MODREV_1, (void *)&modldrv, NULL
123};
124
125
126int
127_init(void)
128{
129	int error;
130
131	/* Initialize the soft state structures */
132	if ((error = ddi_soft_state_init(&grbeep_statep,
133	    sizeof (grbeep_state_t), 1)) != 0) {
134
135		return (error);
136	}
137
138	/* Install the loadable module */
139	if ((error = mod_install(&modlinkage)) != 0) {
140		ddi_soft_state_fini(&grbeep_statep);
141	}
142
143	return (error);
144}
145
146
147int
148_info(struct modinfo *modinfop)
149{
150	return (mod_info(&modlinkage, modinfop));
151}
152
153
154int
155_fini(void)
156{
157	int error;
158
159	error = mod_remove(&modlinkage);
160
161	if (error == 0) {
162		/* Release per module resources */
163		ddi_soft_state_fini(&grbeep_statep);
164	}
165
166	return (error);
167}
168
169
170/*
171 * Beep entry points
172 */
173
174/*
175 * grbeep_attach:
176 */
177static int
178grbeep_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
179{
180	int		instance;
181
182	/* Pointer to soft state */
183	grbeep_state_t	*grbeeptr = NULL;
184
185	GRBEEP_DEBUG1((CE_CONT, "grbeep_attach: Start"));
186
187	switch (cmd) {
188		case DDI_ATTACH:
189			break;
190		case DDI_RESUME:
191
192			return (DDI_SUCCESS);
193		default:
194
195			return (DDI_FAILURE);
196	}
197
198	/* Get the instance and create soft state */
199	instance = ddi_get_instance(dip);
200
201	if (ddi_soft_state_zalloc(grbeep_statep, instance) != 0) {
202
203		return (DDI_FAILURE);
204	}
205
206	grbeeptr = ddi_get_soft_state(grbeep_statep, instance);
207
208	if (grbeeptr == NULL) {
209
210		return (DDI_FAILURE);
211	}
212
213	GRBEEP_DEBUG1((CE_CONT, "grbeeptr = 0x%p, instance %x",
214	    (void *)grbeeptr, instance));
215
216	/* Save the dip */
217	grbeeptr->grbeep_dip = dip;
218
219	/* Initialize beeper mode */
220	grbeeptr->grbeep_mode = GRBEEP_OFF;
221
222	/* Map the Beep Control and Beep counter Registers */
223	if (grbeep_map_regs(dip, grbeeptr) != DDI_SUCCESS) {
224
225		GRBEEP_DEBUG((CE_WARN,
226		    "grbeep_attach: Mapping of beep registers failed."));
227
228		grbeep_cleanup(grbeeptr);
229
230		return (DDI_FAILURE);
231	}
232
233	(void) beep_init((void *)dip, grbeep_on, grbeep_off, grbeep_freq);
234
235	/* Display information in the banner */
236	ddi_report_dev(dip);
237
238	GRBEEP_DEBUG1((CE_CONT, "grbeep_attach: dip = 0x%p done",
239	    (void *)dip));
240
241	return (DDI_SUCCESS);
242}
243
244
245/*
246 * grbeep_detach:
247 */
248static int
249grbeep_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
250{
251	/* Pointer to soft state */
252	grbeep_state_t	*grbeeptr = NULL;
253
254	GRBEEP_DEBUG1((CE_CONT, "grbeep_detach: Start"));
255
256	switch (cmd) {
257		case DDI_SUSPEND:
258			grbeeptr = grbeep_obtain_state(dip);
259
260			if (grbeeptr == NULL) {
261
262				return (DDI_FAILURE);
263			}
264
265			/*
266			 * If a beep is in progress; fail suspend
267			 */
268			if (grbeeptr->grbeep_mode == GRBEEP_OFF) {
269
270				return (DDI_SUCCESS);
271			} else {
272
273				return (DDI_FAILURE);
274			}
275		default:
276
277			return (DDI_FAILURE);
278	}
279}
280
281
282/*
283 * grbeep_info:
284 */
285/* ARGSUSED */
286static int
287grbeep_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
288		void *arg, void **result)
289{
290	dev_t dev;
291	grbeep_state_t  *grbeeptr;
292	int instance, error;
293
294	switch (infocmd) {
295	case DDI_INFO_DEVT2DEVINFO:
296		dev = (dev_t)arg;
297		instance = GRBEEP_UNIT(dev);
298
299		if ((grbeeptr = ddi_get_soft_state(grbeep_statep,
300		    instance)) == NULL) {
301
302			return (DDI_FAILURE);
303		}
304
305		*result = (void *)grbeeptr->grbeep_dip;
306
307		error = DDI_SUCCESS;
308		break;
309	case DDI_INFO_DEVT2INSTANCE:
310		dev = (dev_t)arg;
311		instance = GRBEEP_UNIT(dev);
312
313		*result = (void *)(uintptr_t)instance;
314
315		error = DDI_SUCCESS;
316		break;
317	default:
318		error = DDI_FAILURE;
319
320	}
321
322	return (error);
323}
324
325
326/*
327 * grbeep_freq() :
328 * 	Set beep frequency
329 */
330static void
331grbeep_freq(void *arg, int freq)
332{
333	dev_info_t *dip = (dev_info_t *)arg;
334	grbeep_state_t *grbeeptr = grbeep_obtain_state(dip);
335	int divisor = 0;
336
337	ASSERT(freq != 0);
338
339	GRBEEP_DEBUG1((CE_CONT, "grbeep_freq: dip=0x%p freq=%d mode=%d",
340	    (void *)dip, freq, grbeeptr->grbeep_mode));
341
342	GRBEEP_WRITE_FREQ_CONTROL_REG(GRBEEP_CONTROL);
343
344	divisor = GRBEEP_INPUT_FREQ / freq;
345
346	if (divisor > GRBEEP_DIVISOR_MAX) {
347		divisor = GRBEEP_DIVISOR_MAX;
348	} else if (divisor < GRBEEP_DIVISOR_MIN) {
349		divisor = GRBEEP_DIVISOR_MIN;
350	}
351
352	GRBEEP_DEBUG1((CE_CONT, "grbeep_freq: first=0x%x second=0x%x",
353	    (divisor & 0xff), ((divisor & 0xff00) >> 8)));
354
355	GRBEEP_WRITE_FREQ_DIVISOR_REG(divisor & 0xff);
356	GRBEEP_WRITE_FREQ_DIVISOR_REG((divisor & 0xff00) >> 8);
357}
358
359
360/*
361 * grbeep_on() :
362 *	Turn the beeper on
363 */
364static void
365grbeep_on(void *arg)
366{
367	dev_info_t *dip = (dev_info_t *)arg;
368	grbeep_state_t *grbeeptr = grbeep_obtain_state(dip);
369
370	GRBEEP_DEBUG1((CE_CONT, "grbeep_on: dip = 0x%p mode=%d",
371	    (void *)dip, grbeeptr->grbeep_mode));
372
373	if (grbeeptr->grbeep_mode == GRBEEP_OFF) {
374
375		grbeeptr->grbeep_mode = GRBEEP_ON;
376		GRBEEP_DEBUG1((CE_CONT, "grbeep_on: Starting beep"));
377		GRBEEP_WRITE_START_STOP_REG(GRBEEP_START);
378
379	}
380
381	GRBEEP_DEBUG1((CE_CONT, "grbeep_on: dip = 0x%p done", (void *)dip));
382}
383
384
385/*
386 * grbeep_off() :
387 * 	Turn the beeper off
388 */
389static void
390grbeep_off(void *arg)
391{
392	dev_info_t *dip = (dev_info_t *)arg;
393	grbeep_state_t *grbeeptr = grbeep_obtain_state(dip);
394
395	GRBEEP_DEBUG1((CE_CONT, "grbeep_off: dip = 0x%p mode=%d",
396	    (void *)dip, grbeeptr->grbeep_mode));
397
398	if (grbeeptr->grbeep_mode == GRBEEP_ON) {
399
400		grbeeptr->grbeep_mode = GRBEEP_OFF;
401		GRBEEP_DEBUG1((CE_CONT, "grbeep_off: Stopping beep"));
402		GRBEEP_WRITE_START_STOP_REG(GRBEEP_STOP);
403
404	}
405
406	GRBEEP_DEBUG1((CE_CONT, "grbeep_off: dip = 0x%p done", (void *)dip));
407}
408
409/*
410 * grbeep_map_regs() :
411 *
412 *	The write beep port register and spkr control register
413 *	should be mapped into a non-cacheable portion of the  system
414 *	addressable space.
415 */
416static int
417grbeep_map_regs(dev_info_t *dip, grbeep_state_t *grbeeptr)
418{
419	ddi_device_acc_attr_t attr;
420
421	GRBEEP_DEBUG1((CE_CONT, "grbeep_map_regs: Start"));
422
423	/* The host controller will be little endian */
424	attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
425	attr.devacc_attr_endian_flags  = DDI_STRUCTURE_LE_ACC;
426	attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
427
428	/* Map in operational registers */
429	if (ddi_regs_map_setup(dip, 2,
430	    (caddr_t *)&grbeeptr->grbeep_freq_regs,
431	    0,
432	    sizeof (grbeep_freq_regs_t),
433	    &attr,
434	    &grbeeptr->grbeep_freq_regs_handle)
435	    != DDI_SUCCESS) {
436
437		GRBEEP_DEBUG((CE_CONT, "grbeep_map_regs: Failed to map"));
438		return (DDI_FAILURE);
439	}
440
441	/* Map in operational registers */
442	if (ddi_regs_map_setup(dip, 3,
443	    (caddr_t *)&grbeeptr->grbeep_start_stop_reg,
444	    0,
445	    1,
446	    &attr,
447	    &grbeeptr->grbeep_start_stop_reg_handle)
448	    != DDI_SUCCESS) {
449
450		GRBEEP_DEBUG((CE_CONT, "grbeep_map_regs: Failed to map"));
451		ddi_regs_map_free((void *)&grbeeptr->grbeep_freq_regs_handle);
452
453		return (DDI_FAILURE);
454	}
455
456	GRBEEP_DEBUG1((CE_CONT, "grbeep_map_regs: done"));
457
458	return (DDI_SUCCESS);
459}
460
461
462/*
463 * grbeep_obtain_state:
464 */
465static grbeep_state_t *
466grbeep_obtain_state(dev_info_t *dip)
467{
468	int instance = ddi_get_instance(dip);
469
470	grbeep_state_t *state = ddi_get_soft_state(grbeep_statep, instance);
471
472	ASSERT(state != NULL);
473
474	GRBEEP_DEBUG1((CE_CONT, "grbeep_obtain_state: done"));
475
476	return (state);
477}
478
479
480/*
481 * grbeep_cleanup :
482 *	Cleanup soft state
483 */
484static void
485grbeep_cleanup(grbeep_state_t *grbeeptr)
486{
487	int instance = ddi_get_instance(grbeeptr->grbeep_dip);
488
489	ddi_soft_state_free(grbeep_statep, instance);
490
491	GRBEEP_DEBUG1((CE_CONT, "grbeep_cleanup: done"));
492}
493