/*
 * Glue audio driver for the HP iPAQ H5400
 *
 * Copyright (c) 2000 Nicolas Pitre <nico@cam.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License.
 *
 * This is the machine specific part of the HP iPAQ (aka Bitsy) support.
 * This driver makes use of the AK4535 and the pxa-audio modules.
 *
 * History:
 *
 * 2002-12-18   Jamey Hicks     Adapted from h3600-uda1341.c
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/i2c.h>
#include <linux/kmod.h>
#include <linux/proc_fs.h>

#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/dma.h>
#include <asm/arch-sa1100/h3600_hal.h>
#include <asm/arch/h5400-asic.h>

#include "ak4535.h"
#include "pxa-i2s.h"

#undef DEBUG
#undef DEBUG_PROC
#ifdef DEBUG
#define DPRINTK( x... )  printk( ##x )
#else
#define DPRINTK( x... )
#endif

#define AUDIO_NAME		"H5400"

#define AUDIO_RATE_DEFAULT	44100

/* Put this inside audio_state_t some day.  */
static struct i2c_client *ak4535;

static audio_state_t audio_state;

#ifdef DEBUG_PROC

static int 
write_proc(struct file *file, const char *buffer, unsigned long count, void *data)
{
	char *p = buffer;
	unsigned long i, v;

	i = simple_strtoul (buffer, &p, 0);
	while (*p == ' ')
		p++;
	
	v = simple_strtoul (p, &p, 0);

	printk ("write reg %02x %02x\n", i, v);

	ak4535_write_reg (data, i, v);

	return count;
}

static int 
read_proc(char *page, char **start, off_t off,
	  int count, int *eof, void *data)
{
	int i, len;
	char *p = page;
	
	for (i = 0; i < 0x10; i++) {
		int v = ak4535_read_reg (data, i);
		
		p += sprintf (p, "%02x %02x\n", i, v);
	}

	len = (p - page) - off;
	if (len < 0)
		len = 0;

	*eof = (len <= count) ? 1 : 0;
	*start = page + off;

	return len;
}

static void
delete_proc_debug (void)
{
	remove_proc_entry ("h5400-audio", NULL);
}

static void
create_proc_debug (struct i2c_client *ak)
{
	struct proc_dir_entry *res;

	res = create_proc_entry("h5400-audio", 0, NULL);
	if (res) {
		res->read_proc = read_proc;
		res->write_proc = write_proc;
		res->data = ak;
	}
}

#endif

static int
do_mixer_ioctl(struct i2c_client *client, uint cmd, ulong arg)
{
	/*
	 * We only accept mixer (type 'M') ioctls.
	 */
	if (_IOC_TYPE(cmd) != 'M')
		return -EINVAL;

	return i2c_command(client, cmd, (void *)arg);
}

static int
mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
	return do_mixer_ioctl(file->private_data, cmd, arg);
}

static int mixer_open(struct inode *inode, struct file *file)
{
	struct i2c_client *ak4535;

	ak4535 = i2c_get_client(I2C_DRIVERID_AK4535, I2C_ALGO_PXA, NULL);
	if (ak4535 == NULL) {
		printk("No AK4535 detected.  Is i2c-adap-pxa loaded?\n");
		return -ENODEV;
	}

	file->private_data = ak4535;
	i2c_inc_use_client(ak4535);

	return 0;
}

static int mixer_release(struct inode *inode, struct file *file)
{
	i2c_dec_use_client(file->private_data);
	return 0;
}


static struct file_operations h5400_mixer_fops = {
	open:		mixer_open,
	release:	mixer_release,
	ioctl:		mixer_ioctl,
	owner:		THIS_MODULE
};


/*
 * Audio interface
 */

static long audio_samplerate = AUDIO_RATE_DEFAULT;

static void h5400_set_samplerate(long val)
{
	struct ak4535_cfg cfg;

	audio_samplerate = val;

	cfg.fs = pxa_i2s_set_samplerate (val);
	cfg.format = FMT_I2S;
	i2c_command(ak4535, I2C_AK4535_CONFIGURE, &cfg);
}

static void h5400_audio_init(void *dummy)
{
	h3600_audio_power(0);

	pxa_i2s_init ();
	mdelay(1);

	/* Enable the audio power */
	if (h3600_audio_power(audio_samplerate))
		printk("Unable to enable audio power\n");

	/* Wait for the AK4535 to wake up */
	mdelay(1);

	/* Initialize the AK4535 internal state */
	i2c_command(ak4535, I2C_AK4535_OPEN, 0);

	h3600_audio_mute(0);
}

static void h5400_audio_shutdown(void *dummy)
{
	printk("h5400_audio_shutdown\n");

	/* disable the audio power and all signals leading to the audio chip */
	h3600_audio_mute(1);
	i2c_command(ak4535, I2C_AK4535_CLOSE, 0);

	pxa_i2s_shutdown ();

	h3600_audio_power(0);

#ifdef DEBUG_PROC
	delete_proc_debug();
#endif

	i2c_dec_use_client(ak4535);
	ak4535 = NULL;
}

static int h5400_audio_ioctl(struct inode *inode, struct file *file,
			     uint cmd, ulong arg)
{
	long val;
	int ret = 0;

	/*
	 * These are platform dependent ioctls which are not handled by the
	 * generic pxa-audio module.
	 */
	switch (cmd) {
	case SNDCTL_DSP_STEREO:
		ret = get_user(val, (int *) arg);
		if (ret)
			return ret;
		/* the AK4535 is stereo only */
		ret = (val == 0) ? -EINVAL : 1;
		audio_state.double_samples = 0;
		return put_user(ret, (int *) arg);

	case SNDCTL_DSP_CHANNELS:
	case SOUND_PCM_READ_CHANNELS:
		/* the AK4535 is stereo only */
		audio_state.double_samples = 0;
		return put_user(2, (long *) arg);

	case SNDCTL_DSP_SPEED:
		ret = get_user(val, (long *) arg);
		if (ret) break;
		h5400_set_samplerate(val);
		/* fall through */

	case SOUND_PCM_READ_RATE:
		return put_user(audio_samplerate, (long *) arg);

	case SNDCTL_DSP_SETFMT:
	case SNDCTL_DSP_GETFMTS:
		/* we can do 16-bit only */
		return put_user(AFMT_S16_LE, (long *) arg);

	case SOUND_MIXER_WRITE_RECSRC:
	case SOUND_MIXER_READ_RECSRC:
		return i2c_command(ak4535, cmd, (long *) arg);

	default:
		/* Maybe this is meant for the mixer (As per OSS Docs) */
		return do_mixer_ioctl(ak4535, cmd, arg);
	}

	return ret;
}


static audio_state_t audio_state = {
	output_stream:		&pxa_i2s_output_stream,
	input_stream:		&pxa_i2s_input_stream,
	hw_init:		h5400_audio_init,
	hw_shutdown:		h5400_audio_shutdown,
	client_ioctl:		h5400_audio_ioctl,
	sem:			__MUTEX_INITIALIZER(audio_state.sem),
};

static int h5400_audio_open(struct inode *inode, struct file *file)
{
	int err;

	ak4535 = i2c_get_client(I2C_DRIVERID_AK4535, I2C_ALGO_PXA, NULL);
	if (ak4535 == NULL) {
		printk("No AK4535 detected.  Is i2c-adap-pxa loaded?\n");
		return -ENODEV;
	}

	err = pxa_audio_attach(inode, file, &audio_state);
	if (err == 0)
		i2c_inc_use_client(ak4535);

#ifdef DEBUG_PROC
	create_proc_debug(ak4535);
#endif

	return err;
}

/*
 * Missing fields of this structure will be patched with the call
 * to pxa_audio_attach().
 */
static struct file_operations h5400_audio_fops = {
	open:		h5400_audio_open,
	owner:		THIS_MODULE
};


static int audio_dev_id, mixer_dev_id;

static int __init h5400_module_init(void)
{
	if (!machine_is_h5400 ())
		return -ENODEV;

	/* register devices */
	audio_dev_id = register_sound_dsp(&h5400_audio_fops, -1);
	mixer_dev_id = register_sound_mixer(&h5400_mixer_fops, -1);

	/* try to bring in i2c support */
	request_module("i2c-adap-pxa");
	request_module("ak4535");

	printk( KERN_INFO "iPAQ H5400 audio support initialized\n" );
	return 0;
}

static void __exit h5400_module_exit(void)
{
	unregister_sound_dsp(audio_dev_id);
	unregister_sound_mixer(mixer_dev_id);
}

module_init(h5400_module_init);
module_exit(h5400_module_exit);

MODULE_AUTHOR("Nicolas Pitre, George France, Jamey Hicks, Phil Blundell");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Glue audio driver for the HP iPAQ H5400");

EXPORT_NO_SYMBOLS;
