/*
 * Driver for piezo & magnetic buzzers on TRAB board
 * Copyright (C) 2002, 2003
 * DENX Software Engineering, Wolfgang Denk, wd@denx.de
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/buzzer.h>

#include <asm/uaccess.h>
#include <asm/arch/irqs.h>
#include <asm/arch/hardware.h>

#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif

#define	DRIVER_VERSION		"$Revision: 1.6 $"

#define BUZZER_MAJOR		115
#define PCLK			66000000
#define BUZZER_DEFAULT_FREC	400

#if 0
#define DPRINTK(args...) printk(##args)
#else
#define DPRINTK(args...)
#endif

#if 0
#define SILENCE
#endif

#define bSTART0		(1 << 0)
#define bUPDATE0	(1 << 1)
#define bINVERT0	(1 << 2)
#define bRELOAD0	(1 << 3)

#define bSTART1		(1 << 8)
#define bUPDATE1	(1 << 9)
#define bINVERT1	(1 << 10)
#define bRELOAD1	(1 << 11)

#define bSTART2		(1 << 12)
#define bUPDATE2	(1 << 13)
#define bINVERT2	(1 << 14)
#define bRELOAD2	(1 << 15)

static void buzzer_handler (int irq, void *dev_id, struct pt_regs *regs);

struct buzzer_data_s;
typedef struct buzzer_data_s buzzer_data_t;

struct buzzer_data_s
{
	int  (*init) (buzzer_data_t *buzzer);
	void (*term) (buzzer_data_t *buzzer);
	void (*start)(buzzer_data_t *buzzer);
	void (*stop) (buzzer_data_t *buzzer);
	void (*turn) (buzzer_data_t *buzzer, int on);
	int  (*freq) (buzzer_data_t *buzzer, uint *hz);

	char *name;
	int ready;
	int mode;
	uint hz;
	uint period;
	int on;
	uint cntb;
	uint cnt;
#ifdef CONFIG_PROC_FS
	char *proc;
#endif
};

/********************************
 **** Piezo buzzer routines *****
 ********************************/

static int buzzer_piezo_init (buzzer_data_t *buzzer)
{
	if (request_irq (IRQ_TIMER2, buzzer_handler, 0, "buzzer", buzzer)) {
		printk (KERN_ERR "%s: unable to register isr\n", buzzer->name);
		return -ENODEV;
	}

	rTCFG0 &= ~0xFF00;
	rTCFG0 |=  0x0F00;

	rTCFG1 &= ~0xF00;
	rTCFG1 |=  0x300;

	return 0;
}

static void buzzer_piezo_term (buzzer_data_t *buzzer)
{  
	free_irq (IRQ_TIMER2, buzzer);
}

static void buzzer_piezo_start (buzzer_data_t *buzzer)
{
	rTCON = (rTCON | bUPDATE2 | bRELOAD2) & ~bINVERT2;
	rTCON = (rTCON | bSTART2) & ~bUPDATE2;
}

static void buzzer_piezo_stop (buzzer_data_t *buzzer)
{
	rTCON &= ~(bSTART2 | bRELOAD2);
}

static void buzzer_piezo_turn (buzzer_data_t *buzzer, int on)
{
        DPRINTK ("%s: %s\n", buzzer->name, on ? "+++++" : "-----");

	rPDCON &= ~0xC000;

	if (on) {
#ifndef SILENCE
		rPDCON |= 0x8000;
#endif
	} else {
		rPDCON |= 0x4000;
		rPDDAT &= ~0x80;
	}

	buzzer->on = on;
}

static int buzzer_piezo_freq (buzzer_data_t *buzzer, uint *hz)
{
	uint counter;
		
	if (*hz == 0) return -1;
	
	counter = (PCLK / *hz) >> 9;
	
	counter = counter >= 2 ? counter : 2;
	counter = counter <= 0xFFFF ? counter : 0xFFFF;

	rTCNTB2 = counter;
	rTCMPB2 = counter / 2;

	*hz = buzzer->hz = (PCLK / counter) >> 9;
	
	return 0;
}

static buzzer_data_t buzzer_piezo =
{
	init:	buzzer_piezo_init,
	term:	buzzer_piezo_term,
	start:	buzzer_piezo_start,
	stop:	buzzer_piezo_stop,
	turn:	buzzer_piezo_turn,
	freq:	buzzer_piezo_freq,
	
	name:	"piezo buzzer",
#ifdef CONFIG_PROC_FS
	proc:	"driver/buzzer_piezo",
#endif
};

/********************************
 **** Magnetic buzzer routines **
 ********************************/

static int buzzer_magnetic_init (buzzer_data_t *buzzer)
{
	if (request_irq (IRQ_TIMER0, buzzer_handler, 0, "buzzer", buzzer)) {
		printk (KERN_ERR "%s: unable to register isr\n", buzzer->name);
		return -ENODEV;
	}

	rTCFG0 &= ~0xFF;
	rTCFG0 |=  0x0F;

	rTCFG1 &= ~0xFF;
	rTCFG1 |=  0x23;

	return 0;
}

static void buzzer_magnetic_term (buzzer_data_t *buzzer)
{  
	free_irq (IRQ_TIMER0, buzzer);
}

static void buzzer_magnetic_start (buzzer_data_t *buzzer)
{
	rTCON = (rTCON | bUPDATE0 | bRELOAD0) & ~bINVERT0;
	rTCON = (rTCON | bUPDATE1 | bRELOAD1) & ~bINVERT1;

	rTCON = (rTCON | bSTART0) & ~bUPDATE0;
	rTCON = (rTCON | bSTART1) & ~bUPDATE1;
}

static void buzzer_magnetic_stop (buzzer_data_t *buzzer)
{
	rTCON &= ~(bSTART0 | bRELOAD0);

	rTCON &= ~(bSTART1 | bRELOAD1);
}

static void buzzer_magnetic_turn (buzzer_data_t *buzzer, int on)
{
	DPRINTK ("%s: %s\n", buzzer->name, on ? "+++++" : "-----");

	rPDCON &= ~0x3C00;

	if (on) {
#ifndef SILENCE
		rPDCON |= 0x2800;
#endif
	} else {
		rPDCON |= 0x1400;
		rPDDAT &= ~0x60;
	}

	buzzer->on = on;
}

static int buzzer_magnetic_freq (buzzer_data_t *buzzer, uint *hz)
{
	uint counter;
		
	if (*hz == 0) return -1;
	
	counter = (PCLK / *hz) >> 9;

	counter = counter >= 2 ? counter : 2;
	counter = counter <= 0xFFFF ? counter : 0xFFFF;

	rTCNTB0 = counter;
	rTCMPB0 = counter / 2;

	rTCNTB1 = counter;
	rTCMPB1 = counter * 2 / 3;

	*hz = buzzer->hz = (PCLK / counter) >> 9;
	
	return 0;
}

static buzzer_data_t buzzer_magnetic =
{
	init:	buzzer_magnetic_init,
	term:	buzzer_magnetic_term,
	start:	buzzer_magnetic_start,
	stop:	buzzer_magnetic_stop,
	turn:	buzzer_magnetic_turn,
	freq:	buzzer_magnetic_freq,
	
	name:	"magnetic buzzer",
#ifdef CONFIG_PROC_FS
	proc:	"driver/buzzer_mag",
#endif
};

/********************************
 **** Local routines ************
 ********************************/

static buzzer_data_t *buzzers [] =
{
	& buzzer_piezo,
	& buzzer_magnetic,
};

static buzzer_data_t *buzzer_get (struct inode *inode)
{
	buzzer_data_t *buzzer;
	int minor = MINOR (inode->i_rdev);
	
	if (minor < sizeof (buzzers) / sizeof (buzzers [0]) &&
	    buzzers [minor]->ready)
	{
		buzzer = buzzers [minor];
	}
	else
	{
		buzzer = NULL;
	}
	
	return buzzer;
}

static int buzzer_change_period (buzzer_data_t *buzzer, uint period)
{
	DPRINTK ("%s: %s %ums\n", buzzer->name, __FUNCTION__, period);

	if (buzzer->mode != BUZZER_CMD_OFF) buzzer->stop (buzzer);

	buzzer->period = period;
	buzzer->cntb = buzzer->cnt = buzzer->period * buzzer->hz / 1000;

	if (buzzer->mode != BUZZER_CMD_OFF) buzzer->start (buzzer);

	return 0;
}

static int buzzer_change_freq (buzzer_data_t *buzzer, uint *hz)
{
	DPRINTK("%s: %s %uHz\n", buzzer->name, __FUNCTION__, *hz);

	if (buzzer->mode != BUZZER_CMD_OFF) buzzer->stop (buzzer);

	if (buzzer->freq (buzzer, hz) < 0) return -EINVAL;
	buzzer->cntb = buzzer->cnt = buzzer->period * buzzer->hz / 1000;
	
	if (buzzer->mode != BUZZER_CMD_OFF) buzzer->start (buzzer);

	return 0;
}

static int buzzer_mode_on (buzzer_data_t *buzzer)
{
	DPRINTK("%s: %s\n", buzzer->name, __FUNCTION__);

	if (buzzer->mode == BUZZER_CMD_OFF) {
		buzzer->start (buzzer);
	}
	buzzer->turn (buzzer, 1);
	buzzer->mode = BUZZER_CMD_ON;

	return 0;
}

static int buzzer_mode_off (buzzer_data_t *buzzer)
{
	DPRINTK("%s: %s\n", buzzer->name, __FUNCTION__);

	buzzer->stop (buzzer);
	buzzer->turn (buzzer, 0);
	buzzer->mode = BUZZER_CMD_OFF;
	
	return 0;
}

static int buzzer_mode_monoflop (buzzer_data_t *buzzer)
{
	DPRINTK("%s: %s\n", buzzer->name, __FUNCTION__);

	if (buzzer->period == 0) return -EINVAL;
	
	buzzer->cntb = buzzer->period * buzzer->hz / 1000;
	buzzer->cnt = buzzer->cntb;
	
	if (buzzer->mode == BUZZER_CMD_OFF)
	{
		buzzer->start (buzzer);
	}
	buzzer->turn (buzzer, 1);
	buzzer->mode = BUZZER_CMD_MONOFLOP;

	return 0;
}

static int buzzer_mode_periodic (buzzer_data_t *buzzer)
{
	DPRINTK("%s: %s\n", buzzer->name, __FUNCTION__);

	if (buzzer->period == 0) return -EINVAL;

	buzzer->cntb = buzzer->period * buzzer->hz / 1000;
	buzzer->cnt = buzzer->cntb;
	
	if (buzzer->mode == BUZZER_CMD_OFF)
	{
		buzzer->start (buzzer);
	}
	buzzer->turn (buzzer, 1);
	buzzer->mode = BUZZER_CMD_PERIODIC;

	return 0;
}

/********************************
 **** Driver interface **********
 ********************************/

static void buzzer_handler (int irq, void *dev_id, struct pt_regs *regs)
{
	buzzer_data_t *buzzer = dev_id;

	switch (buzzer->mode) {
	case BUZZER_CMD_MONOFLOP:
		if (--buzzer->cnt == 0) {
			buzzer_mode_off (buzzer);
		}
		break;

	case BUZZER_CMD_PERIODIC:
		if (--buzzer->cnt == 0) {
			buzzer->turn (buzzer, !buzzer->on);
			buzzer->cnt = buzzer->cntb;
		}
		break;
	}
}
static int buzzer_open (struct inode *inode, struct file *file)
{
	buzzer_data_t *buzzer = buzzer_get (inode);

	if (buzzer == NULL) return -ENXIO;

	return 0;
}

static int buzzer_cmd (buzzer_data_t *buzzer, uint cmd, uint *param)
{
	int res;

	switch (cmd) {
	case BUZZER_CMD_SET_FREQ:
		res = buzzer_change_freq (buzzer, param);
		break;

	case BUZZER_CMD_SET_PERIOD:
		res = buzzer_change_period (buzzer, *param);
		break;

	case BUZZER_CMD_ON:
		res = buzzer_mode_on (buzzer);
		break;

	case BUZZER_CMD_OFF:
		res = buzzer_mode_off (buzzer);
		break;

	case BUZZER_CMD_MONOFLOP:
		res = buzzer_mode_monoflop (buzzer);
		break;

	case BUZZER_CMD_PERIODIC:
		res = buzzer_mode_periodic (buzzer);
		break;

	default:
		res = -ENOTTY;
	}

	return res;
}


static int buzzer_ioctl (struct inode *inode, struct file *file,
                         uint cmd, unsigned long arg)
{
	buzzer_data_t *buzzer = buzzer_get (inode);
	int res;
	uint param;

	if (buzzer == NULL) return -ENODEV;

	copy_from_user (&param, (uint *)arg, sizeof(uint));
	
	res = buzzer_cmd (buzzer, cmd, & param);

	if (cmd == BUZZER_CMD_SET_FREQ && res == 0)
		copy_to_user ((uint *)arg, &param, sizeof(uint));

	return res;
}

static struct file_operations buzzer_fops =
{
	open:		buzzer_open,
	ioctl:		buzzer_ioctl,
};

#ifdef CONFIG_PROC_FS
static int buzzer_proc_read (char *page, char **start,
                              off_t off, int count, int *eof, void *data)
{
	buzzer_data_t *buzzer = (buzzer_data_t *) data;
	char *out = page;
	char *mode;
	int len;
	char *fmt = "mode: %s\n" "frequency=%uHz\n" "period=%ums\n";

	switch (buzzer->mode) {
	case BUZZER_CMD_ON:
		mode = "on";
		break;

	case BUZZER_CMD_OFF:
		mode = "off";
		break;

	case BUZZER_CMD_MONOFLOP:
		mode = "monoflop";
		break;

	case BUZZER_CMD_PERIODIC:
		mode = "periodic";
		break;

	default:
		mode = "???";
		break;
	}

	out += sprintf (out, fmt, mode, buzzer->hz, buzzer->period);

	len = out - page;
	len -= off;

	if (len < count) {
		*eof = 1;
		if (len < 0) {
			len = 0;
		}
	} else {
		len = count;
	}

	if (len) {
		*start = page + off;
	}

	return len;
}

static int buzzer_proc_write (struct file *file, const char *buffer,
                              unsigned long count, void *data)
{
	buzzer_data_t *buzzer = (buzzer_data_t *) data;
	int res = -EINVAL;
	char buf[20];
	struct
	{
		char *name;
		uint io;
	}
	cmd [] =
	{
		  /* cmd=param
		   */
		{ "freq",	BUZZER_CMD_SET_FREQ },
		{ "period",	BUZZER_CMD_SET_PERIOD },

		  /* cmd
		   */
		{ "on",		BUZZER_CMD_ON },
		{ "off",	BUZZER_CMD_OFF },
		{ "monoflop",	BUZZER_CMD_MONOFLOP },
		{ "periodic",	BUZZER_CMD_PERIODIC },
	};
	int i;
	uint param = 0;

	if (count > sizeof (buf))
		return -EINVAL;

	if (copy_from_user(buf, buffer, count)) {
		return -EFAULT;
	}

	for (i = 0; i < sizeof(cmd) / sizeof(cmd [0]);  i ++) {
		uint len = strlen (cmd[i].name);

		if (memcmp (buf, cmd [i].name, len) == 0) {
			char *end;

			if (i < 2 && buf [len] == '=') {
				param = simple_strtol (buf + len + 1, &end, 0);
				if (*end != '\n') continue;
			} else if (i >= 2 && buf [len] == '\n') {
				param = 0;
			} else {
				continue;
			}
			res = buzzer_cmd (buzzer, cmd[i].io, & param);
			break;
		}
	}
	
	if (i == sizeof(cmd) / sizeof(cmd [0])) {
		return -EINVAL;
	}
	
	return count;
}
#endif

static int __init buzzer_init (void)
{
	int i;
	u_int freq = BUZZER_DEFAULT_FREC;
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry *entry;
#endif

	if (register_chrdev (BUZZER_MAJOR, "buzzer", & buzzer_fops)) {
		printk (KERN_ERR "buzzer: register_chrdev failed\n");
		return -EIO;
	}

	for (i = 0; i < sizeof (buzzers) / sizeof (buzzers [0]); i ++) {
    		buzzer_data_t *buzzer = buzzers [i];

		if (buzzer->init (buzzer)) {
			printk (KERN_ERR "buzzer: init %s failed\n",
				buzzer->name);
			buzzer->ready = 0;
		} else {
			buzzer->ready = 1;
		}

#ifdef CONFIG_PROC_FS
		entry = create_proc_entry (buzzer->proc, 
					   S_IFREG|S_IRUGO|S_IWUGO,
					   NULL);
		if (entry == NULL) {
			printk("Unable to create /proc/%s entry\n", buzzer->proc);
			return -EINVAL;
		}
		entry->read_proc = buzzer_proc_read;
		entry->write_proc = buzzer_proc_write;
		entry->data = buzzer;
#endif

		buzzer->mode = BUZZER_CMD_OFF;
		buzzer->freq (buzzer, & freq);
		buzzer->period = 0;

		buzzer->turn (buzzer, 0);
	}

	printk (KERN_INFO "TRAB buzzer driver " DRIVER_VERSION "\n");

	return 0;
}

/******************************
 **** Module Declarations *****
 **************************** */

module_init (buzzer_init);

#ifdef MODULE

static void buzzer_cleanup (void)
{
	int i;

	for (i = 0; i < sizeof (buzzers) / sizeof (buzzers [0]); i ++) {
    		buzzer_data_t *buzzer = buzzers [i];

		buzzer->turn (buzzer, 0);
		buzzer->stop (buzzer);
		buzzer->term (buzzer);
#ifdef CONFIG_PROC_FS
		remove_proc_entry (buzzer->proc, NULL);
#endif
	}

	if (unregister_chrdev (BUZZER_MAJOR, "buzzer")) {
		printk (KERN_ERR "buzzer: failed to unregister character device\n");
	}
}

module_exit (buzzer_cleanup);

#endif	/* MODULE */
