root/usr/src/uts/common/io/beep.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * This is the Beep module for supporting keyboard beep for keyboards
 * that do not have the beeping feature within themselves
 *
 */

#include <sys/types.h>
#include <sys/conf.h>

#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/ddi_impldefs.h>
#include <sys/kmem.h>

#include <sys/beep.h>
#include <sys/inttypes.h>

/*
 * Debug stuff
 * BEEP_DEBUG used for errors
 * BEEP_DEBUG1 prints when beep_debug > 1 and used for normal messages
 */
#ifdef DEBUG
int beep_debug = 0;
#define BEEP_DEBUG(args)        if (beep_debug) cmn_err args
#define BEEP_DEBUG1(args)       if (beep_debug > 1) cmn_err args
#else
#define BEEP_DEBUG(args)
#define BEEP_DEBUG1(args)
#endif

int beep_queue_size = BEEP_QUEUE_SIZE;

/*
 * Note that mutex_init is not called on the mutex in beep_state,
 * But assumes that zeroed memory does not need to call mutex_init,
 * as documented in mutex.c
 */

beep_state_t beep_state;

beep_params_t beep_params[] = {
        {BEEP_CONSOLE,  900,    200},
        {BEEP_TYPE4,    2000,   0},
        {BEEP_DEFAULT,  1000,   200},   /* Must be last */
};


/*
 * beep_init:
 * Allocate the beep_queue structure
 * Initialize beep_state structure
 * Called from beep driver attach routine
 */

int
beep_init(void *arg,
    beep_on_func_t beep_on_func,
    beep_off_func_t beep_off_func,
    beep_freq_func_t beep_freq_func)
{
        beep_entry_t *queue;

        BEEP_DEBUG1((CE_CONT,
            "beep_init(0x%lx, 0x%lx, 0x%lx, 0x%lx) : start.",
            (unsigned long) arg,
            (unsigned long) beep_on_func,
            (unsigned long) beep_off_func,
            (unsigned long) beep_freq_func));

        mutex_enter(&beep_state.mutex);

        if (beep_state.mode != BEEP_UNINIT) {
                mutex_exit(&beep_state.mutex);
                BEEP_DEBUG((CE_WARN,
                    "beep_init : beep_state already initialized."));
                return (DDI_SUCCESS);
        }

        queue = kmem_zalloc(sizeof (beep_entry_t) * beep_queue_size,
            KM_SLEEP);

        BEEP_DEBUG1((CE_CONT,
            "beep_init : beep_queue kmem_zalloc(%d) = 0x%lx.",
            (int)sizeof (beep_entry_t) * beep_queue_size,
            (unsigned long)queue));

        if (queue == NULL) {
                BEEP_DEBUG((CE_WARN,
                    "beep_init : kmem_zalloc of beep_queue failed."));
                return (DDI_FAILURE);
        }

        beep_state.arg = arg;
        beep_state.mode = BEEP_OFF;
        beep_state.beep_freq = beep_freq_func;
        beep_state.beep_on = beep_on_func;
        beep_state.beep_off = beep_off_func;
        beep_state.timeout_id = 0;

        beep_state.queue_head = 0;
        beep_state.queue_tail = 0;
        beep_state.queue_size = beep_queue_size;
        beep_state.queue = queue;

        mutex_exit(&beep_state.mutex);

        BEEP_DEBUG1((CE_CONT, "beep_init : done."));
        return (DDI_SUCCESS);
}


int
beep_fini(void)
{
        BEEP_DEBUG1((CE_CONT, "beep_fini() : start."));

        (void) beeper_off();

        mutex_enter(&beep_state.mutex);

        if (beep_state.mode == BEEP_UNINIT) {
                mutex_exit(&beep_state.mutex);
                BEEP_DEBUG((CE_WARN,
                    "beep_fini : beep_state already uninitialized."));
                return (0);
        }

        if (beep_state.queue != NULL)
                kmem_free(beep_state.queue,
                    sizeof (beep_entry_t) * beep_state.queue_size);

        beep_state.arg = (void *)NULL;
        beep_state.mode = BEEP_UNINIT;
        beep_state.beep_freq = (beep_freq_func_t)NULL;
        beep_state.beep_on = (beep_on_func_t)NULL;
        beep_state.beep_off = (beep_off_func_t)NULL;
        beep_state.timeout_id = 0;

        beep_state.queue_head = 0;
        beep_state.queue_tail = 0;
        beep_state.queue_size = 0;
        beep_state.queue = (beep_entry_t *)NULL;

        mutex_exit(&beep_state.mutex);

        BEEP_DEBUG1((CE_CONT, "beep_fini() : done."));

        return (0);
}


int
beeper_off(void)
{
        BEEP_DEBUG1((CE_CONT, "beeper_off : start."));

        mutex_enter(&beep_state.mutex);

        if (beep_state.mode == BEEP_UNINIT) {
                mutex_exit(&beep_state.mutex);
                return (ENXIO);
        }

        if (beep_state.mode == BEEP_TIMED) {
                (void) untimeout(beep_state.timeout_id);
                beep_state.timeout_id = 0;
        }

        if (beep_state.mode != BEEP_OFF) {
                beep_state.mode = BEEP_OFF;

                if (beep_state.beep_off != NULL)
                        (*beep_state.beep_off)(beep_state.arg);
        }

        beep_state.queue_head = 0;
        beep_state.queue_tail = 0;

        mutex_exit(&beep_state.mutex);

        BEEP_DEBUG1((CE_CONT, "beeper_off : done."));

        return (0);
}

int
beeper_freq(enum beep_type type, int freq)
{
        beep_params_t *bp;

        BEEP_DEBUG1((CE_CONT, "beeper_freq(%d, %d) : start", type, freq));

        /*
         * The frequency value is limited to the range of [0 - 32767]
         */
        if (freq < 0 || freq > INT16_MAX)
                return (EINVAL);

        for (bp = beep_params; bp->type != BEEP_DEFAULT; bp++) {
                if (bp->type == type)
                        break;
        }

        if (bp->type != type) {
                BEEP_DEBUG((CE_WARN, "beeper_freq : invalid type."));

                return (EINVAL);
        }

        bp->frequency = freq;

        BEEP_DEBUG1((CE_CONT, "beeper_freq : done."));
        return (0);
}

/*
 * beep :
 *      Start beeping for period specified by the type value,
 *      from the value in the beep_param structure in milliseconds.
 */
int
beep(enum beep_type type)
{

        beep_params_t *bp;

        BEEP_DEBUG1((CE_CONT, "beep(%d) : start.", type));

        for (bp = beep_params; bp->type != BEEP_DEFAULT; bp++) {
                if (bp->type == type)
                        break;
        }

        if (bp->type != type) {

                BEEP_DEBUG((CE_WARN, "beep : invalid type."));

                /* If type doesn't match, return silently without beeping */
                return (EINVAL);
        }

        return (beep_mktone(bp->frequency, bp->duration));
}


/*ARGSUSED*/
int
beep_polled(enum beep_type type)
{
        /*
         * No-op at this time.
         *
         * Don't think we can make this work in general, as tem_safe
         * has a requirement of no mutexes, but kbd sends messages
         * through streams.
         */

        BEEP_DEBUG1((CE_CONT, "beep_polled(%d)", type));

        return (0);
}

/*
 * beeper_on :
 *      Turn the beeper on
 */
int
beeper_on(enum beep_type type)
{
        beep_params_t *bp;
        int status = 0;

        BEEP_DEBUG1((CE_CONT, "beeper_on(%d) : start.", type));

        for (bp = beep_params; bp->type != BEEP_DEFAULT; bp++) {
                if (bp->type == type)
                        break;
        }

        if (bp->type != type) {

                BEEP_DEBUG((CE_WARN, "beeper_on : invalid type."));

                /* If type doesn't match, return silently without beeping */
                return (EINVAL);
        }

        mutex_enter(&beep_state.mutex);

        if (beep_state.mode == BEEP_UNINIT) {
                status = ENXIO;

        /* Start another beep only if the previous one is over */
        } else if (beep_state.mode == BEEP_OFF) {
                if (bp->frequency != 0) {
                        beep_state.mode = BEEP_ON;

                        if (beep_state.beep_freq != NULL)
                                (*beep_state.beep_freq)(beep_state.arg,
                                    bp->frequency);

                        if (beep_state.beep_on != NULL)
                                (*beep_state.beep_on)(beep_state.arg);
                }
        } else {
                status = EBUSY;
        }

        mutex_exit(&beep_state.mutex);

        BEEP_DEBUG1((CE_CONT, "beeper_on : done, status %d.", status));

        return (status);
}


int
beep_mktone(int frequency, int duration)
{
        int next;
        int status = 0;

        BEEP_DEBUG1((CE_CONT, "beep_mktone(%d, %d) : start.", frequency,
            duration));

        /*
         * The frequency value is limited to the range of [0 - 32767]
         */
        if (frequency < 0 || frequency > INT16_MAX)
                return (EINVAL);

        mutex_enter(&beep_state.mutex);

        if (beep_state.mode == BEEP_UNINIT) {
                status = ENXIO;

        } else if (beep_state.mode == BEEP_TIMED) {

                /* If already processing a beep, queue this one */

                if (frequency != 0) {
                        next = beep_state.queue_tail + 1;
                        if (next == beep_state.queue_size)
                                next = 0;

                        if (next != beep_state.queue_head) {
                                /*
                                 * If there is room in the queue,
                                 * add this entry
                                 */

                                beep_state.queue[beep_state.queue_tail].
                                    frequency = (unsigned short)frequency;

                                beep_state.queue[beep_state.queue_tail].
                                    duration = (unsigned short)duration;

                                beep_state.queue_tail = next;
                        } else {
                                status = EAGAIN;
                        }
                }

        } else if (beep_state.mode == BEEP_OFF) {

                /* Start another beep only if the previous one is over */

                if (frequency != 0) {
                        beep_state.mode = BEEP_TIMED;

                        if (beep_state.beep_freq != NULL)
                                (*beep_state.beep_freq)(beep_state.arg,
                                    frequency);

                        if (beep_state.beep_on != NULL)
                                (*beep_state.beep_on)(beep_state.arg);

                        /*
                         * Set timeout for ending the beep after the
                         * specified time
                         */

                        beep_state.timeout_id = timeout(beep_timeout, NULL,
                            drv_usectohz(duration * 1000));
                }
        } else {
                status = EBUSY;
        }

        mutex_exit(&beep_state.mutex);

        BEEP_DEBUG1((CE_CONT, "beep_mktone : done, status %d.", status));

        return (status);
}


/*
 * Turn the beeper off which had been turned on from beep()
 * for a specified period of time
 */
/*ARGSUSED*/
void
beep_timeout(void *arg)
{
        int frequency;
        int duration;
        int next;

        BEEP_DEBUG1((CE_CONT, "beeper_timeout : start."));

        mutex_enter(&beep_state.mutex);

        beep_state.timeout_id = 0;

        if (beep_state.mode == BEEP_UNINIT) {
                mutex_exit(&beep_state.mutex);
                BEEP_DEBUG1((CE_CONT, "beep_timeout : uninitialized."));
                return;
        }

        if ((beep_state.mode == BEEP_ON) ||
            (beep_state.mode == BEEP_TIMED)) {

                beep_state.mode = BEEP_OFF;

                if (beep_state.beep_off != NULL)
                        (*beep_state.beep_off)(beep_state.arg);
        }

        if (beep_state.queue_head != beep_state.queue_tail) {

                next = beep_state.queue_head;

                frequency = beep_state.queue[next].frequency;

                duration = beep_state.queue[next].duration;

                next++;
                if (next == beep_state.queue_size)
                        next = 0;

                beep_state.queue_head = next;

                beep_state.mode = BEEP_TIMED;

                if (frequency != 0) {
                        if (beep_state.beep_freq != NULL)
                                (*beep_state.beep_freq)(beep_state.arg,
                                    frequency);

                        if (beep_state.beep_on != NULL)
                                (*beep_state.beep_on)(beep_state.arg);
                }

                /* Set timeout for ending the beep after the specified time */

                beep_state.timeout_id = timeout(beep_timeout, NULL,
                    drv_usectohz(duration * 1000));
        }

        mutex_exit(&beep_state.mutex);

        BEEP_DEBUG1((CE_CONT, "beep_timeout : done."));
}


/*
 * Return true (1) if we are sounding a tone.
 */
int
beep_busy(void)
{
        int status;

        BEEP_DEBUG1((CE_CONT, "beep_busy : start."));

        mutex_enter(&beep_state.mutex);

        status = beep_state.mode != BEEP_UNINIT &&
            beep_state.mode != BEEP_OFF;

        mutex_exit(&beep_state.mutex);

        BEEP_DEBUG1((CE_CONT, "beep_busy : status %d.", status));

        return (status);
}