
/*********************************************************************
 *                
 * Filename:      rtcmosram.c
 * Description:   rtcmosram is a kernel module that serves to interface
 *                with the RT/CMOS RAM in IBM ThinkPads
 * Author:        Thomas Hood
 * Created:       24 July 1999 
 *
 * Please report bugs to the author ASAP.
 * 
 *     Copyright (c) 1999 J.D. Thomas Hood, All rights reserved
 *     
 *     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 program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *     GNU General Public License for more details.
 * 
 *     To receive a copy of the GNU General Public License, please write
 *     to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 *     Boston, MA 02111-1307 USA
 *     
 ********************************************************************/

#include "thinkpad_driver.h"

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/proc_fs.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/mc146818rtc.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "thinkpad_common.h"
#include "rtcmosram.h"


/****** definitions ******/


/****** declarations ******/

extern struct proc_dir_entry *thinkpad_ppde;

#ifdef CONFIG_DEVFS_FS
static int rtcmosram_open(
	struct inode * pinodeThe,
	struct file * pfileThe
);
static int rtcmosram_release(
	struct inode * pinodeThe,
	struct file * pfileThe 
);
static int rtcmosram_ioctl(
	struct inode * pinodeThe,
	struct file * pfileThe,
	unsigned int uintIoctlNum,
	unsigned long ulongIoctlArg
);
#endif

/****** variables ******/

static const char _szMyName[] = "rtcmosram";
static const char _szImName[] = "rtcmosram_do";
static const char _szMyVersion[] = "4.0";
static const char _szProcfile[] = "driver/thinkpad/rtcmosram";
static struct resource *_presourceRtcmosram;

MODULE_AUTHOR( "Thomas Hood" );
MODULE_DESCRIPTION( "Driver for the RT CMOS RAM device on IBM ThinkPads" );
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,10)
MODULE_LICENSE( "GPL" );
#endif

#ifdef CONFIG_DEVFS_FS
static struct file_operations _fileopsRtcmosram = {
	.ioctl   = rtcmosram_ioctl,
	.open    = rtcmosram_open,
	.release = rtcmosram_release,
	.owner   = THIS_MODULE
};

static devfs_handle_t _devfs_handleRtcmosram;
#endif

/****** functions *******/

/*
 * We use _p variants just for safety.  The rtc_lock spinlock keeps us
 * from fighting with the rtc driver.
 */
static byte cmos_getb( byte bWhere )
{
	byte bGot;
	unsigned long flags;

	spin_lock_irqsave(&rtc_lock,flags);
	outb_p( bWhere, 0x70 );
	bGot = inb_p( 0x71 );
	outb_p( 0x0f, 0x70 ); /* required, according to Tech Ref */
	inb( 0x71 );
	spin_unlock_irqrestore(&rtc_lock,flags);

	return bGot;
}


static void cmos_putb( byte bWhat, byte bWhere )
{
	unsigned long flags;

	spin_lock_irqsave(&rtc_lock,flags);
	outb_p( bWhere, 0x70 );
	outb_p( bWhat, 0x71 );
	outb_p( 0x0F, 0x70 ); /* required, according to Tech Ref */
	inb( 0x71 );
	spin_unlock_irqrestore(&rtc_lock,flags);

	return;
}


static int rtcmosram_read_proc(
	char *pchBuf, 
	char **ppchStart, 
	off_t off,
	int intCount, 
	int *pintEof, 
	void *data
) {
	return snprintf(
		pchBuf, intCount,
		"%s version %s accessing RT CMOS RAM at ioports 0x%x, 0x%x\n",
		_szMyName, _szMyVersion,
		0x70, 0x71
	);
}


int rtcmosram_do( 
	unsigned long ulongIoctlArg,
	flag_t fCallerHasWritePerm
) {
	rtcmosram_ioparm_t ioparmMy;
	unsigned long ulRtnCopy;

	ulRtnCopy = copy_from_user(
		(byte *)&ioparmMy,
		(byte *)(rtcmosram_ioparm_t *)ulongIoctlArg,
		sizeof( ioparmMy )
	);
	if ( ulRtnCopy ) return -EFAULT;

	switch ( ioparmMy.in.wFunc ) {

		case RTCMOSRAM_FUNC_GETDATA: {
			rtcmosram_data_t dataThe;
			int i;

			for ( i=0; i<sizeof(dataThe); i++ )
				*(((byte *)&dataThe)+i) = cmos_getb( (byte)i );

			ulRtnCopy =  copy_to_user(
				(byte *)&(((rtcmosram_ioparm_t *)ulongIoctlArg)->data),
				(byte *)&dataThe,
				sizeof(dataThe)
			);
			if ( ulRtnCopy ) return -EFAULT;

			return 0;
		}

		case RTCMOSRAM_FUNC_DAYLIGHTSAVINGTIME_ABLIFY:
			if ( ! fCallerHasWritePerm )
				return -EACCES;

			if ( (flag_t)ioparmMy.in.dwParm1 ) {
				cmos_putb( cmos_getb(0xB) | 1, 0xB );
			} else {
				cmos_putb( cmos_getb(0xB) & ~1, 0xB );
			}
			return 0;

		case RTCMOSRAM_FUNC_DAYLIGHTSAVINGTIME_GET: {
			byte bGot;
			bGot = cmos_getb( 0xB );
			ioparmMy.out.wRtn = 0;
			ioparmMy.out.dwParm1 = (bGot & 1) ? (dword)1 : (dword)0;
			ulRtnCopy = copy_to_user(
				(byte *)(rtcmosram_ioparm_t *)ulongIoctlArg,
				(byte *)&ioparmMy,
				sizeof(ioparmMy)
			);
			if ( ulRtnCopy ) return -EFAULT;
			return 0;
		}

		default:
			printk(KERN_ERR
				"%s: Function %d not recognized\n",
				_szMyName, ioparmMy.in.wFunc
			);
			return -EINVAL;
	} /* switch */

	return -ETHINKPAD_PROGRAMMING; /* we should not reach here */
}


#ifdef CONFIG_DEVFS_FS
static int rtcmosram_open(
	struct inode * pinodeThe,
	struct file * pfileThe
) {

	return 0;
}


static int rtcmosram_release(
	struct inode * pinodeThe,
	struct file * pfileThe
) {

	return 0;
}


static flag_t caller_has_w( struct file * pfileThe )
{
	return ((pfileThe->f_mode) & FMODE_WRITE) ? 1 : 0;
}


static int rtcmosram_ioctl(
	struct inode * pinodeThe,
	struct file * pfileThe,
	unsigned int uintIoctlNum,
	unsigned long ulongIoctlArg
) {

#ifdef DEBUG_VERBOSE
	printk( "%s: Doing ioctl number 0x%x\n", _szMyName, uintIoctlNum );
#endif

	switch ( uintIoctlNum ) {
		/* should  return  at the end of each case block */

		case IOCTL_RTCMOSRAM_REQUEST: 
			return rtcmosram_do(
				ulongIoctlArg, 
				caller_has_w( pfileThe )
			);

		default:
			/* ioctl number not recognized -- do nothing */
			return -ENOTTY;

	} /* switch */
			
	return -ETHINKPAD_PROGRAMMING; /* We should never arrive here */
}
#endif /* defined(CONFIG_DEVFS_FS) */

static int __init rtcmosram_init( void )
{

	_presourceRtcmosram = request_region( 0x70, 2, _szMyName );
	if ( _presourceRtcmosram == NULL ) {
#if 0
		printk(KERN_ERR
			"%s: I/O ports for RT CMOS RAM not available -- aborting.\n",
			_szMyName
		);
		return -EBUSY;
#else
		/*
		 * In some configs this region is already claimed by "rtc"
		 * Don't worry about it, since we take rtc_lock when we
		 * access the CMOS RAM
		 */
		printk(KERN_INFO
			"%s: I/O ports for RT CMOS RAM not available, but ignoring this.\n",
			_szMyName
		);
#endif
	}

	if ( !thinkpad_ppde || !create_proc_read_entry(
		_szProcfile,
		S_IFREG | S_IRUGO,
		NULL,
		rtcmosram_read_proc,
		NULL
	) ) {
		printk(KERN_ERR
			"%s: Could not create_proc_read_entry() /proc/%s\n",
			_szMyName, _szProcfile
		);
	}
	/* proc entry created */

#ifdef CONFIG_DEVFS_FS
	_devfs_handleRtcmosram = devfs_register(
		NULL /* dir */,
		"thinkpad/rtcmosram",
		DEVFS_FL_DEFAULT | DEVFS_FL_AUTO_DEVNUM,
		0 /* major */, 0 /* minor */,
		S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH,
		&_fileopsRtcmosram,
		NULL /* info */
	);
	if ( _devfs_handleRtcmosram == NULL ) {
		printk(KERN_ERR "%s: Could not devfs_register(). Aborting.\n", _szMyName );
		return -EIO;
	}
	/* devfs entry created */
#endif

	inter_module_register( _szImName, THIS_MODULE, &rtcmosram_do );

	return 0;
}


static void __exit rtcmosram_exit( void )
{

	inter_module_unregister( _szImName );

#ifdef CONFIG_DEVFS_FS
	devfs_unregister( _devfs_handleRtcmosram );
#endif

 	remove_proc_entry( _szProcfile, NULL );

	if ( _presourceRtcmosram != NULL ) release_resource( _presourceRtcmosram );

	return;
}

module_init(rtcmosram_init);
module_exit(rtcmosram_exit);
