/*
 * linux/drivers/ide/handtune.c
 *
 * Copyright (c) 2000 Linux Torvalds & authors
 *
 * works only compiled as a module !
 */

/*
 * Author: Samuel Thibault <samuel.thibault@ens-lyon.org>
 */

#include <linux/module.h>
#include <linux/malloc.h>
#include <linux/blkdev.h>
#include <linux/errno.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include "qd65xx.h"

const char timing_name[] = "timing";

extern void qd_set_timing(ide_drive_t *drive, int timing);

static int qd_shift(char *q)
{
	if ( *q >= '0' && *q <= '9')
		*q -= '0';
				
	if ( *q >= 'a' && *q <= 'f')
		*q -= 'a' - 10;

	if ( *q >= 'A' && *q <= 'F')
		*q -= 'A' - 10;

	return (*q >= '\020');
	/* accepts \007\016 as 0x7e */
}

static int qd_get_digit(const char p[])
{
	char q[] = "  ";
	byte *r1 = (byte *) q;
	byte *r2 = (byte *) q+1;
	q[0]=p[0];
	q[1]=p[1];
	if (qd_shift(&q[0]) || qd_shift(&q[1])) return -1;
	return((*r1 << 4) | *r2);
}

static int qd_read_proc_timing
	(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	ide_drive_t *drive = (ide_drive_t *) data;
	int len;
	char *out = page;
	out += sprintf(out, "%s timing : %#x on %#x\n",drive->name,QD_TIMING(drive),QD_TIMREG(drive));
	len = out - page;
	PROC_IDE_READ_RETURN(page,start,off,count,eof,len);
}

static int qd_write_proc_timing
	(struct file *file, const char *buffer, unsigned long count, void *data)
{
	ide_drive_t *drive = (ide_drive_t *) data;
	int timing;

	if (count<2) return -1;

	timing = qd_get_digit(buffer);

	if (timing<0) return -1;

	printk(KERN_DEBUG "timing %#x\n",timing);

	qd_set_timing(drive, timing);

	return 2;
}

static void qd_add_proc_drive(ide_drive_t *drive)
{
	struct proc_dir_entry *dir = drive->proc;
	struct proc_dir_entry *ent;
	
	if (!dir)
		return;
	
	ent = create_proc_entry(timing_name,S_IFREG|S_IRUGO|S_IWUSR,dir);
	if (!ent) return;
	ent->nlink = 1;
	ent->data = drive;
	ent->read_proc = qd_read_proc_timing;
	ent->write_proc = qd_write_proc_timing;
}

static void qd_remove_proc_drive(ide_drive_t *drive)
{
	struct proc_dir_entry *dir = drive->proc;
	
	if (!dir)
		return;

	remove_proc_entry(timing_name,dir);
}

static void qd_add_proc_hwif(ide_hwif_t *hwif)
{
	if (hwif->chipset != ide_qd65xx)
		return;

	qd_add_proc_drive(&hwif->drives[0]);
	qd_add_proc_drive(&hwif->drives[1]);
}

static void qd_remove_proc_hwif(ide_hwif_t *hwif)
{
	if (hwif->chipset != ide_qd65xx)
		return;

	qd_remove_proc_drive(&hwif->drives[0]);
	qd_remove_proc_drive(&hwif->drives[1]);
}

int /*__init*/ init_module (void)
{
	qd_add_proc_hwif(&ide_hwifs[0]);
	qd_add_proc_hwif(&ide_hwifs[1]);
	return 0;
}

int /*__exit*/ cleanup_module (void)
{
	qd_remove_proc_hwif(&ide_hwifs[0]);
	qd_remove_proc_hwif(&ide_hwifs[1]);
	return 0;
}
