/***********************************************************************
 *
 * (C) Copyright 2005
 * DENX Software Engineering
 * Wolfgang Denk, wd@denx.de
 * All rights reserved.
 *
 ***********************************************************************/

#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/rtc.h>
#include <asm/time.h>
#include <asm/uaccess.h>
#include <asm/mpc5xxx.h>
#include <asm/io.h>
#include <linux/buzzer.h>

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

#ifdef CONFIG_UBOOT
#include <asm/ppcboot.h>
#endif

MODULE_DESCRIPTION("INKA4x0 Buzzer Driver");

#define DEVICE_NAME	"buzzer"
#define BUZZER_MAJOR	115
#define BUZZER_GPT	(MPC5xxx_GPT + 0x60)	/* GPT6 */

#ifdef CONFIG_UBOOT
extern unsigned char __res[];
#define MPC5xxx_IPBFREQ (((bd_t *)__res)->bi_ipbfreq)
#else
#define MPC5xxx_IPBFREQ CONFIG_PPC_5xxx_IPBFREQ	/* Assume Hz, not MHz */
#endif

#undef DEBUG
#ifdef DEBUG
#define DEBUGK(args...) printk(args)
#else
#define DEBUGK(args...)
#endif

#define PERIOD_TO_JIFFIES(t) ((t) * HZ / 1000)

typedef struct {
	unsigned int freq;
	unsigned int period;
	int mode;
	int is_on;
} buzzer_info_t;

buzzer_info_t buzzer = {
	BUZZER_DEF_FREQ,
	BUZZER_DEF_PERIOD,
	BUZZER_OFF,
	0
};

static struct timer_list buzzer_timer;
static struct proc_dir_entry *buzzer_proc_root = NULL;

/*
 * Forward declarations
 */
static void buzzer_timer_handler(unsigned long data);
static void buzzer_cleanup(void);

/*
 * Local functions
 */
static void buzzer_config(void)
{
	DEBUGK(__FUNCTION__ ": IPB Freq=%ld\n", MPC5xxx_IPBFREQ);

	init_timer(&buzzer_timer);
	buzzer_timer.function = buzzer_timer_handler;
	buzzer_timer.data = 0;
}

static void buzzer_turn_on(void)
{
	struct mpc5xxx_gpt* gpt = (struct mpc5xxx_gpt*)(BUZZER_GPT);

	const u32 prescale = MPC5xxx_IPBFREQ / buzzer.freq / 128;
	const u32 count = 128;
	const u32 width = 64;

	out_be32(&gpt->cir, (prescale << 16) | count);
	out_be32(&gpt->cr, width << 16);
	out_be32(&gpt->emsr, 3);	/* Timer enabled for PWM */
}

static void buzzer_turn_off(void)
{
	struct mpc5xxx_gpt* gpt = (struct mpc5xxx_gpt*)(BUZZER_GPT);
	out_be32(&gpt->emsr, 0);
}

static void buzzer_set_period (unsigned int period)
{
	if (!period)
		printk(DEVICE_NAME ": Zero period is senseless\n");
	else
		buzzer.period = period;
}

static void buzzer_set_freq (unsigned int freq)
{
	/* avoid zero prescale in buzzer_turn_on() */
	if (freq > MPC5xxx_IPBFREQ / 128)
		printk(DEVICE_NAME ": %dHz exceeds maximum (%dHz)\n",
		       freq, MPC5xxx_IPBFREQ / 128);
	else if (!freq)
		printk(DEVICE_NAME ": Zero frequency is senseless\n");
	else
		buzzer.freq = freq;
}

static void buzzer_set_mode (unsigned int mode)
{
	DEBUGK(__FUNCTION__ ": mode=%d\n", mode);

	del_timer_sync(&buzzer_timer);

	switch (mode) {

	case BUZZER_ON:
		buzzer_turn_on();
		buzzer.mode = BUZZER_ON;
		break;

	case BUZZER_OFF:
		buzzer_turn_off();
		buzzer.mode = BUZZER_OFF;
		break;

	case BUZZER_MONOFLOP:
		buzzer.mode = BUZZER_MONOFLOP;
		init_timer(&buzzer_timer);
		buzzer_timer.expires = (jiffies +
					PERIOD_TO_JIFFIES(buzzer.period));
		buzzer_turn_on();
		add_timer(&buzzer_timer);
		break;

	case BUZZER_PERIODIC:
		buzzer.mode = BUZZER_PERIODIC;
		init_timer(&buzzer_timer);
		buzzer_timer.expires = (jiffies +
					PERIOD_TO_JIFFIES(buzzer.period));
		buzzer_turn_on();
		buzzer.is_on = 1;
		add_timer(&buzzer_timer);
		break;
	}
}

static int buzzer_cmd (unsigned int cmd, unsigned int *param)
{
	DEBUGK(__FUNCTION__ ": cmd=%d param=%d\n", cmd, *param);

	switch (cmd) {
	case BUZZER_SET_FREQ:
		buzzer_set_freq(*param);
		if (buzzer.mode == BUZZER_ON) {
			buzzer_turn_off();
			buzzer_turn_on();
		}
		break;

	case BUZZER_GET_FREQ:
		*param = buzzer.freq;
		break;

	case BUZZER_SET_PERIOD:
		buzzer_set_period(*param);
		break;

	case BUZZER_GET_PERIOD:
		*param = buzzer.period;
		break;

	case BUZZER_SET_MODE:
		buzzer_set_mode(*param);
		break;

	case BUZZER_GET_MODE:
		*param = buzzer.mode;
		break;

	default:
		return -ENOTTY;
	}
	return 0;
}

static void buzzer_timer_handler(unsigned long data)
{
	DEBUGK(__FUNCTION__ ": mode = %d\n", buzzer.mode);

	switch (buzzer.mode) {
	case BUZZER_MONOFLOP:
		buzzer_turn_off();
		break;
	case BUZZER_PERIODIC:
		if (buzzer.is_on) {
			buzzer_turn_off();
			buzzer.is_on = 0;
		} else {
			buzzer_turn_on();
			buzzer.is_on = 1;
		}
		init_timer(&buzzer_timer);
		buzzer_timer.expires = (jiffies +
					PERIOD_TO_JIFFIES(buzzer.period));
		add_timer(&buzzer_timer);
		break;
	}
}

/*
 * Driver interface funtions
 */
static int buzzer_open (struct inode * inode, struct file * file)
{
	DEBUGK(__FUNCTION__ "() called\n");
	MOD_INC_USE_COUNT;
	return 0;
}

static int buzzer_release (struct inode *inode, struct file *file)
{
	DEBUGK(__FUNCTION__ "() called\n");
	MOD_DEC_USE_COUNT;
	return 0;
}

static int buzzer_ioctl (struct inode * inode, struct file * file,
			 uint cmd, unsigned long arg)
{
	int err;
	unsigned int param;

	/*
	 * Do as much checks as possible from the command definition.
	 */
	if (_IOC_TYPE(cmd) != BUZZER_MAGIC) return -ENOTTY;

	if (_IOC_DIR(cmd) & _IOC_WRITE) {
		if ((err = get_user(param, (unsigned int *)arg)) != 0)
			return err;
	}

	if ((err = buzzer_cmd(cmd, &param)) != 0)
		return err;

	if (_IOC_DIR(cmd) & _IOC_READ) {
		if ((err = put_user(param, (unsigned int *)arg)) != 0)
			return err;
	}

	return 0;
}

static struct file_operations buzzer_fops =
{
	owner:	THIS_MODULE,
	open:	buzzer_open,
	release:	buzzer_release,
	ioctl:	buzzer_ioctl,
};

#ifdef CONFIG_PROC_FS
/*
 * Buzzer control via /proc/buzzer
 */
typedef struct {
	char *name;
	unsigned int set_cmd;
	unsigned int get_cmd;
} buzzer_cmds_t;

#define BUZZER_CMD_NUM 3
static buzzer_cmds_t buzzer_cmds[BUZZER_CMD_NUM] = {
	{ "freq", BUZZER_SET_FREQ, BUZZER_GET_FREQ,},
	{ "period", BUZZER_SET_PERIOD, BUZZER_GET_PERIOD },
	{ "mode", BUZZER_SET_MODE, BUZZER_GET_MODE }
};
#define  BUZZER_MODE_NUM 4
static char *buzzer_modes[BUZZER_MODE_NUM] = {
	"off", "on", "monoflop", "periodic"
};

static int
buzzer_write_proc (struct file *file, const char *buffer,
		   unsigned long count, void *data)
{
	char tmp[16];
	int len, cmd = buzzer_cmds[(int)data].set_cmd;
	unsigned int val;

	if (!count)
		return (count);

	len = sizeof(tmp) - 1;
	if (count < len)
		len = count;

	copy_from_user (tmp, buffer, len);

	tmp[len] = '\0';

	DEBUGK(__FUNCTION__ ": %s=%s\n",
	       buzzer_cmds[(int)data].name, tmp);

	if (cmd == BUZZER_SET_MODE) {
		for (val = BUZZER_MODE_NUM - 1; val > 0; val--) {
			if (!strncmp(tmp, buzzer_modes[val],
				     strlen(buzzer_modes[val])))
				break;
		}
	} else {
		val = simple_strtoul ((const char *)&tmp, NULL, 0);
	}

	buzzer_cmd(cmd, &val);
	return (len);
}

static int
buzzer_read_proc (char *page, char **start, off_t off,
		  int count, int *eof, void *data)
{
	int cmd = buzzer_cmds[(int)data].get_cmd;
	unsigned int val;
	int len;

	buzzer_cmd(cmd, &val);
	DEBUGK(__FUNCTION__ ": %s=%d\n", buzzer_cmds[(int)data].name, val);
	if (cmd == BUZZER_GET_MODE) {
		len = sprintf (page, "%s\n", buzzer_modes[val]);
	} else {
		len = sprintf (page, "%d\n", val);
	}
	*eof = 1;
	return len;
}
#endif /* CONFIG_PROC_FS */


static int __init buzzer_init (void)
{
	int rc, i;

	/*
	 * Configure the buzzer handling
	 */
	buzzer_config();

	/*
	 * Register the character device
	 */
	rc = register_chrdev (BUZZER_MAJOR, DEVICE_NAME, &buzzer_fops);
	if (rc < 0) {
		printk (DEVICE_NAME ": Couldn't register major %d (rc=%d)\n",
			BUZZER_MAJOR, rc);
		return rc;
	}

	printk (KERN_INFO DEVICE_NAME ": $Revision: 1824 $ wg@denx.de\n");

#ifdef CONFIG_PROC_FS
	/*
	 * Setup /proc/buzzer for buzzer control
	 */
	if ((buzzer_proc_root = proc_mkdir(DEVICE_NAME, NULL)) == NULL) {
		printk (DEVICE_NAME ": proc_mkdir buzzer failed\n");
		buzzer_cleanup();
		return -ENXIO;
	}

	for (i = 0; i < BUZZER_CMD_NUM; i++) {
		struct proc_dir_entry *e;
		if ((e = create_proc_entry(buzzer_cmds[i].name, S_IRUGO | S_IWUSR,
					   buzzer_proc_root)) == NULL) {
			printk (DEVICE_NAME ": create_proc_entry %s failed\n",
				buzzer_cmds[i].name);
			buzzer_cleanup();
		}
		e->write_proc = buzzer_write_proc;
		e->read_proc  = buzzer_read_proc;
		e->data = (void *)i;
	}
#endif
	return 0;
}

/*
 * Module declatrations
 */

module_init (buzzer_init);

static void buzzer_cleanup (void)
{
	int rc;

	DEBUGK(__FUNCTION__ "() called\n");

	/*
	 * Shut-up
	 */
	buzzer_set_mode(BUZZER_OFF);

#ifdef CONFIG_PROC_FS
	/*
	 * Remove PROC files in /proc/buffer
	 */
	if (buzzer_proc_root != NULL) {
		int i;
		for (i = 0; i < BUZZER_CMD_NUM; i++)
			remove_proc_entry (buzzer_cmds[i].name, buzzer_proc_root);
		remove_proc_entry ("buzzer", NULL);
	}
#endif

	/*
	 * Unregister the device
	 */
	rc = unregister_chrdev (BUZZER_MAJOR, DEVICE_NAME);
	if (rc < 0) {
		printk (DEVICE_NAME "unregister_chrdev: error %d\n", rc);
	}
}

module_exit (buzzer_cleanup);
