/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Gigabeat specific code for the Wolfson codec
 *
 * Based on code from the ipodlinux project - http://ipodlinux.org/
 * Adapted for Rockbox in December 2005
 *
 * Original file: linux/arch/armnommu/mach-ipod/audio.c
 *
 * Copyright (c) 2003-2005 Bernard Leach (leachbj@bouncycastle.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "cpu.h"
#include "kernel.h"
#include "sound.h"
#include "i2c-s3c2440.h"
#include "system-target.h"
#include "timer.h"
#include "wmcodec.h"

#ifdef HAVE_HARDWARE_BEEP
#include "misc.h"
static void beep_stop(void);
#endif

void audiohw_init(void)
{
    /* GPC5 controls headphone output */
    GPCCON &= ~(0x3 << 10);
    GPCCON |= (1 << 10);
    GPCDAT |= (1 << 5);

    audiohw_preinit();

#ifdef HAVE_HARDWARE_BEEP
    /* pin pullup ON */
    GPBUP &= ~(1 << 3);
    beep_stop();
    /* set pin to TIMER3 output (functional TOUT3) */
    GPBCON = (GPBCON & ~(0x3 << 6)) | (2 << 6);
#endif
}

void wmcodec_write(int reg, int data)
{
    unsigned char d[2];
    d[0] = (reg << 1) | ((data & 0x100) >> 8);
    d[1] = data;
    i2c_write(0x34, d, 2);
}

#ifdef HAVE_HARDWARE_BEEP
/** Beeping via TIMER3 output to codec MONO input **/
static int beep_cycles = 0;
static int beep_cycle_count = 0;

static void beep_stop(void)
{
    int oldstatus = disable_interrupt_save(IRQ_FIQ_STATUS);

    /* stop interrupt */
    INTMSK |= TIMER3_MASK;
    /* stop timer */
    TCON &= ~(1 << 16);
    /* be sure timer PWM pin is LOW to avoid noise */
    TCON ^= (GPBDAT & (1 << 3)) << 15;
    /* clear pending */
    SRCPND = TIMER3_MASK;
    INTPND = TIMER3_MASK;

    restore_interrupt(oldstatus);
}

/* Timer interrupt called on every cycle */
void TIMER3(void)
{
    if (++beep_cycles >= beep_cycle_count)
    {
        /* beep is complete */
        beep_stop();
    }

    /* clear pending */
    SRCPND = TIMER3_MASK;
    INTPND = TIMER3_MASK;
}

void beep_play(unsigned int frequency, unsigned int duration,
               unsigned int amplitude)
{
    #define TIMER3_TICK_SEC (TIMER_FREQ / TIMER234_PRESCALE)

    unsigned long tcnt, tcmp;
    int oldstatus;

    if (frequency == 0 || duration == 0 || amplitude == 0)
    {
        beep_stop(); /* won't hear it anyway */
        return;
    }

    /* pretend this is pcm */
    if (amplitude > 32767)
        amplitude = 32767;

    /* limit frequency range to keep math in range */
    if (frequency > 19506)
        frequency = 19506;
    else if (frequency < 18)
        frequency = 18;

    /* set timer */
    tcnt = TIMER3_TICK_SEC / frequency;

    oldstatus = disable_interrupt_save(IRQ_FIQ_STATUS);

    beep_cycles = 0;
    beep_cycle_count = TIMER3_TICK_SEC * duration / (tcnt*1000);

    /* divider = 1/2 */
    TCFG1 = (TCFG1 & ~(0xf << 12)) | (0 << 12);
    /* stop TIMER3, inverter OFF */
    TCON &= ~((1 << 18) | (1 << 16));
    /* set countdown */
    TCNTB3 = tcnt;
    /* set PWM counter - control volume with duty cycle. */
    tcmp = tcnt*amplitude / (65536*2 - 2*amplitude);
    TCMPB3 = tcmp < 1 ? 1 : tcmp;
    /* manual update: on (to reset count), interval mode (auto reload) */
    TCON |= (1 << 19) | (1 << 17);
    /* clear manual bit */
    TCON &= ~(1 << 17);
    /* start timer */
    TCON |= (1 << 16);
    /* enable timer interrupt */
    INTMSK &= ~TIMER3_MASK;

    restore_interrupt(oldstatus);
}
#endif /* HAVE_HARDWARE_BEEP */
