/*
 * PC Speaker Driver
 * main.c
 *
 * Copyright (C) 2016 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "speaker.h"
#include <timer.h>
#include <device.h>

const char driver_name[] = "speaker";

static dword_t speaker_init(void);
static dword_t speaker_cleanup(void);
static dword_t speaker_ioctl(device_t *device,
                             dword_t control_code,
                             const void *in_buffer,
                             size_t in_length,
                             void *out_buffer,
                             size_t out_length);

static char_dev_driver_t speaker_driver =
{
    .init_proc = speaker_init,
    .cleanup_proc = speaker_cleanup,
    .ioctl_proc = speaker_ioctl
};

static void speaker_on(dword_t frequency)
{
    dword_t divisor = TIMER_BASE_FREQUENCY / frequency;

    cpu_write_port_byte(TIMER_CMD_PORT, TIMER_SQUARE_WAVE(2));
    cpu_write_port_byte(TIMER_CHANNEL_PORT(2), (byte_t) (divisor & 0xFF));
    cpu_write_port_byte(TIMER_CHANNEL_PORT(2), (byte_t) (divisor >> 8));
    cpu_write_port_byte(0x61, cpu_read_port_byte(0x61) | 3);
}

static void speaker_off()
{
    cpu_write_port_byte(0x61, cpu_read_port_byte(0x61) & 0xFC);
}

static dword_t speaker_init(void)
{
    return ERR_SUCCESS;
}

static dword_t speaker_cleanup(void)
{
    speaker_off();
    return ERR_SUCCESS;
}

static dword_t speaker_ioctl(device_t *device,
                             dword_t control_code,
                             const void *in_buffer,
                             size_t in_length,
                             void *out_buffer,
                             size_t out_length)
{
    switch (control_code)
    {
    case SPEAKER_IOCTL_ON:
        if (in_length < sizeof(dword_t)) return ERR_SMALLBUF;
        speaker_on(*(dword_t*)in_buffer);
        return ERR_SUCCESS;

    case SPEAKER_IOCTL_OFF:
        speaker_off();
        return ERR_SUCCESS;

    default:
        return ERR_INVALID;
    }
}

dword_t driver_load(const char *parameters)
{
    return register_char_dev_driver(&speaker_driver);
}
