xref: /openbmc/linux/drivers/char/toshiba.c (revision 1da177e4)
11da177e4SLinus Torvalds /* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops
21da177e4SLinus Torvalds  *
31da177e4SLinus Torvalds  * Copyright (c) 1996-2001  Jonathan A. Buzzard (jonathan@buzzard.org.uk)
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  * Valuable assistance and patches from:
61da177e4SLinus Torvalds  *     Tom May <tom@you-bastards.com>
71da177e4SLinus Torvalds  *     Rob Napier <rnapier@employees.org>
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  * Fn status port numbers for machine ID's courtesy of
101da177e4SLinus Torvalds  *     0xfc02: Scott Eisert <scott.e@sky-eye.com>
111da177e4SLinus Torvalds  *     0xfc04: Steve VanDevender <stevev@efn.org>
121da177e4SLinus Torvalds  *     0xfc08: Garth Berry <garth@itsbruce.net>
131da177e4SLinus Torvalds  *     0xfc0a: Egbert Eich <eich@xfree86.org>
141da177e4SLinus Torvalds  *     0xfc10: Andrew Lofthouse <Andrew.Lofthouse@robins.af.mil>
151da177e4SLinus Torvalds  *     0xfc11: Spencer Olson <solson@novell.com>
161da177e4SLinus Torvalds  *     0xfc13: Claudius Frankewitz <kryp@gmx.de>
171da177e4SLinus Torvalds  *     0xfc15: Tom May <tom@you-bastards.com>
181da177e4SLinus Torvalds  *     0xfc17: Dave Konrad <konrad@xenia.it>
191da177e4SLinus Torvalds  *     0xfc1a: George Betzos <betzos@engr.colostate.edu>
201da177e4SLinus Torvalds  *     0xfc1b: Munemasa Wada <munemasa@jnovel.co.jp>
211da177e4SLinus Torvalds  *     0xfc1d: Arthur Liu <armie@slap.mine.nu>
221da177e4SLinus Torvalds  *     0xfc5a: Jacques L'helgoualc'h <lhh@free.fr>
231da177e4SLinus Torvalds  *     0xfcd1: Mr. Dave Konrad <konrad@xenia.it>
241da177e4SLinus Torvalds  *
251da177e4SLinus Torvalds  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
261da177e4SLinus Torvalds  *
271da177e4SLinus Torvalds  *   This code is covered by the GNU GPL and you are free to make any
281da177e4SLinus Torvalds  *   changes you wish to it under the terms of the license. However the
291da177e4SLinus Torvalds  *   code has the potential to render your computer and/or someone else's
301da177e4SLinus Torvalds  *   unusable. Please proceed with care when modifying the code.
311da177e4SLinus Torvalds  *
321da177e4SLinus Torvalds  * Note: Unfortunately the laptop hardware can close the System Configuration
331da177e4SLinus Torvalds  *       Interface on it's own accord. It is therefore necessary for *all*
341da177e4SLinus Torvalds  *       programs using this driver to be aware that *any* SCI call can fail at
351da177e4SLinus Torvalds  *       *any* time. It is up to any program to be aware of this eventuality
361da177e4SLinus Torvalds  *       and take appropriate steps.
371da177e4SLinus Torvalds  *
381da177e4SLinus Torvalds  * This program is free software; you can redistribute it and/or modify it
391da177e4SLinus Torvalds  * under the terms of the GNU General Public License as published by the
401da177e4SLinus Torvalds  * Free Software Foundation; either version 2, or (at your option) any
411da177e4SLinus Torvalds  * later version.
421da177e4SLinus Torvalds  *
431da177e4SLinus Torvalds  * This program is distributed in the hope that it will be useful, but
441da177e4SLinus Torvalds  * WITHOUT ANY WARRANTY; without even the implied warranty of
451da177e4SLinus Torvalds  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
461da177e4SLinus Torvalds  * General Public License for more details.
471da177e4SLinus Torvalds  *
481da177e4SLinus Torvalds  * The information used to write this driver has been obtained by reverse
491da177e4SLinus Torvalds  * engineering the software supplied by Toshiba for their portable computers in
501da177e4SLinus Torvalds  * strict accordance with the European Council Directive 92/250/EEC on the legal
511da177e4SLinus Torvalds  * protection of computer programs, and it's implementation into English Law by
521da177e4SLinus Torvalds  * the Copyright (Computer Programs) Regulations 1992 (S.I. 1992 No.3233).
531da177e4SLinus Torvalds  *
541da177e4SLinus Torvalds  */
551da177e4SLinus Torvalds 
561da177e4SLinus Torvalds #define TOSH_VERSION "1.11 26/9/2001"
571da177e4SLinus Torvalds #define TOSH_DEBUG 0
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds #include <linux/module.h>
601da177e4SLinus Torvalds #include <linux/kernel.h>
611da177e4SLinus Torvalds #include <linux/sched.h>
621da177e4SLinus Torvalds #include <linux/types.h>
631da177e4SLinus Torvalds #include <linux/fcntl.h>
641da177e4SLinus Torvalds #include <linux/miscdevice.h>
651da177e4SLinus Torvalds #include <linux/ioport.h>
661da177e4SLinus Torvalds #include <asm/io.h>
671da177e4SLinus Torvalds #include <asm/uaccess.h>
681da177e4SLinus Torvalds #include <linux/init.h>
691da177e4SLinus Torvalds #include <linux/stat.h>
701da177e4SLinus Torvalds #include <linux/proc_fs.h>
711da177e4SLinus Torvalds 
721da177e4SLinus Torvalds #include <linux/toshiba.h>
731da177e4SLinus Torvalds 
741da177e4SLinus Torvalds #define TOSH_MINOR_DEV 181
751da177e4SLinus Torvalds 
761da177e4SLinus Torvalds static int tosh_id = 0x0000;
771da177e4SLinus Torvalds static int tosh_bios = 0x0000;
781da177e4SLinus Torvalds static int tosh_date = 0x0000;
791da177e4SLinus Torvalds static int tosh_sci = 0x0000;
801da177e4SLinus Torvalds static int tosh_fan = 0;
811da177e4SLinus Torvalds 
821da177e4SLinus Torvalds static int tosh_fn = 0;
831da177e4SLinus Torvalds 
841da177e4SLinus Torvalds module_param(tosh_fn, int, 0);
851da177e4SLinus Torvalds 
861da177e4SLinus Torvalds 
871da177e4SLinus Torvalds static int tosh_ioctl(struct inode *, struct file *, unsigned int,
881da177e4SLinus Torvalds 	unsigned long);
891da177e4SLinus Torvalds 
901da177e4SLinus Torvalds 
911da177e4SLinus Torvalds static struct file_operations tosh_fops = {
921da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
931da177e4SLinus Torvalds 	.ioctl		= tosh_ioctl,
941da177e4SLinus Torvalds };
951da177e4SLinus Torvalds 
961da177e4SLinus Torvalds static struct miscdevice tosh_device = {
971da177e4SLinus Torvalds 	TOSH_MINOR_DEV,
981da177e4SLinus Torvalds 	"toshiba",
991da177e4SLinus Torvalds 	&tosh_fops
1001da177e4SLinus Torvalds };
1011da177e4SLinus Torvalds 
1021da177e4SLinus Torvalds /*
1031da177e4SLinus Torvalds  * Read the Fn key status
1041da177e4SLinus Torvalds  */
1051da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
1061da177e4SLinus Torvalds static int tosh_fn_status(void)
1071da177e4SLinus Torvalds {
1081da177e4SLinus Torvalds         unsigned char scan;
1091da177e4SLinus Torvalds 	unsigned long flags;
1101da177e4SLinus Torvalds 
1111da177e4SLinus Torvalds 	if (tosh_fn!=0) {
1121da177e4SLinus Torvalds 		scan = inb(tosh_fn);
1131da177e4SLinus Torvalds 	} else {
1141da177e4SLinus Torvalds 		local_irq_save(flags);
1151da177e4SLinus Torvalds 		outb(0x8e, 0xe4);
1161da177e4SLinus Torvalds 		scan = inb(0xe5);
1171da177e4SLinus Torvalds 		local_irq_restore(flags);
1181da177e4SLinus Torvalds 	}
1191da177e4SLinus Torvalds 
1201da177e4SLinus Torvalds         return (int) scan;
1211da177e4SLinus Torvalds }
1221da177e4SLinus Torvalds #endif
1231da177e4SLinus Torvalds 
1241da177e4SLinus Torvalds 
1251da177e4SLinus Torvalds /*
1261da177e4SLinus Torvalds  * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function
1271da177e4SLinus Torvalds  */
1281da177e4SLinus Torvalds static int tosh_emulate_fan(SMMRegisters *regs)
1291da177e4SLinus Torvalds {
1301da177e4SLinus Torvalds 	unsigned long eax,ecx,flags;
1311da177e4SLinus Torvalds 	unsigned char al;
1321da177e4SLinus Torvalds 
1331da177e4SLinus Torvalds 	eax = regs->eax & 0xff00;
1341da177e4SLinus Torvalds 	ecx = regs->ecx & 0xffff;
1351da177e4SLinus Torvalds 
1361da177e4SLinus Torvalds 	/* Portage 610CT */
1371da177e4SLinus Torvalds 
1381da177e4SLinus Torvalds 	if (tosh_id==0xfccb) {
1391da177e4SLinus Torvalds 		if (eax==0xfe00) {
1401da177e4SLinus Torvalds 			/* fan status */
1411da177e4SLinus Torvalds 			local_irq_save(flags);
1421da177e4SLinus Torvalds 			outb(0xbe, 0xe4);
1431da177e4SLinus Torvalds 			al = inb(0xe5);
1441da177e4SLinus Torvalds 			local_irq_restore(flags);
1451da177e4SLinus Torvalds 			regs->eax = 0x00;
1461da177e4SLinus Torvalds 			regs->ecx = (unsigned int) (al & 0x01);
1471da177e4SLinus Torvalds 		}
1481da177e4SLinus Torvalds 		if ((eax==0xff00) && (ecx==0x0000)) {
1491da177e4SLinus Torvalds 			/* fan off */
1501da177e4SLinus Torvalds 			local_irq_save(flags);
1511da177e4SLinus Torvalds 			outb(0xbe, 0xe4);
1521da177e4SLinus Torvalds 			al = inb(0xe5);
1531da177e4SLinus Torvalds 			outb(0xbe, 0xe4);
1541da177e4SLinus Torvalds 			outb (al | 0x01, 0xe5);
1551da177e4SLinus Torvalds 			local_irq_restore(flags);
1561da177e4SLinus Torvalds 			regs->eax = 0x00;
1571da177e4SLinus Torvalds 			regs->ecx = 0x00;
1581da177e4SLinus Torvalds 		}
1591da177e4SLinus Torvalds 		if ((eax==0xff00) && (ecx==0x0001)) {
1601da177e4SLinus Torvalds 			/* fan on */
1611da177e4SLinus Torvalds 			local_irq_save(flags);
1621da177e4SLinus Torvalds 			outb(0xbe, 0xe4);
1631da177e4SLinus Torvalds 			al = inb(0xe5);
1641da177e4SLinus Torvalds 			outb(0xbe, 0xe4);
1651da177e4SLinus Torvalds 			outb(al & 0xfe, 0xe5);
1661da177e4SLinus Torvalds 			local_irq_restore(flags);
1671da177e4SLinus Torvalds 			regs->eax = 0x00;
1681da177e4SLinus Torvalds 			regs->ecx = 0x01;
1691da177e4SLinus Torvalds 		}
1701da177e4SLinus Torvalds 	}
1711da177e4SLinus Torvalds 
1721da177e4SLinus Torvalds 	/* Tecra 700CS/CDT */
1731da177e4SLinus Torvalds 
1741da177e4SLinus Torvalds 	if (tosh_id==0xfccc) {
1751da177e4SLinus Torvalds 		if (eax==0xfe00) {
1761da177e4SLinus Torvalds 			/* fan status */
1771da177e4SLinus Torvalds 			local_irq_save(flags);
1781da177e4SLinus Torvalds 			outb(0xe0, 0xe4);
1791da177e4SLinus Torvalds 			al = inb(0xe5);
1801da177e4SLinus Torvalds 			local_irq_restore(flags);
1811da177e4SLinus Torvalds 			regs->eax = 0x00;
1821da177e4SLinus Torvalds 			regs->ecx = al & 0x01;
1831da177e4SLinus Torvalds 		}
1841da177e4SLinus Torvalds 		if ((eax==0xff00) && (ecx==0x0000)) {
1851da177e4SLinus Torvalds 			/* fan off */
1861da177e4SLinus Torvalds 			local_irq_save(flags);
1871da177e4SLinus Torvalds 			outb(0xe0, 0xe4);
1881da177e4SLinus Torvalds 			al = inb(0xe5);
1891da177e4SLinus Torvalds 			outw(0xe0 | ((al & 0xfe) << 8), 0xe4);
1901da177e4SLinus Torvalds 			local_irq_restore(flags);
1911da177e4SLinus Torvalds 			regs->eax = 0x00;
1921da177e4SLinus Torvalds 			regs->ecx = 0x00;
1931da177e4SLinus Torvalds 		}
1941da177e4SLinus Torvalds 		if ((eax==0xff00) && (ecx==0x0001)) {
1951da177e4SLinus Torvalds 			/* fan on */
1961da177e4SLinus Torvalds 			local_irq_save(flags);
1971da177e4SLinus Torvalds 			outb(0xe0, 0xe4);
1981da177e4SLinus Torvalds 			al = inb(0xe5);
1991da177e4SLinus Torvalds 			outw(0xe0 | ((al | 0x01) << 8), 0xe4);
2001da177e4SLinus Torvalds 			local_irq_restore(flags);
2011da177e4SLinus Torvalds 			regs->eax = 0x00;
2021da177e4SLinus Torvalds 			regs->ecx = 0x01;
2031da177e4SLinus Torvalds 		}
2041da177e4SLinus Torvalds 	}
2051da177e4SLinus Torvalds 
2061da177e4SLinus Torvalds 	return 0;
2071da177e4SLinus Torvalds }
2081da177e4SLinus Torvalds 
2091da177e4SLinus Torvalds 
2101da177e4SLinus Torvalds /*
2111da177e4SLinus Torvalds  * Put the laptop into System Management Mode
2121da177e4SLinus Torvalds  */
2131da177e4SLinus Torvalds int tosh_smm(SMMRegisters *regs)
2141da177e4SLinus Torvalds {
2151da177e4SLinus Torvalds 	int eax;
2161da177e4SLinus Torvalds 
2171da177e4SLinus Torvalds 	asm ("# load the values into the registers\n\t" \
2181da177e4SLinus Torvalds 		"pushl %%eax\n\t" \
2191da177e4SLinus Torvalds 		"movl 0(%%eax),%%edx\n\t" \
2201da177e4SLinus Torvalds 		"push %%edx\n\t" \
2211da177e4SLinus Torvalds 		"movl 4(%%eax),%%ebx\n\t" \
2221da177e4SLinus Torvalds 		"movl 8(%%eax),%%ecx\n\t" \
2231da177e4SLinus Torvalds 		"movl 12(%%eax),%%edx\n\t" \
2241da177e4SLinus Torvalds 		"movl 16(%%eax),%%esi\n\t" \
2251da177e4SLinus Torvalds 		"movl 20(%%eax),%%edi\n\t" \
2261da177e4SLinus Torvalds 		"popl %%eax\n\t" \
2271da177e4SLinus Torvalds 		"# call the System Management mode\n\t" \
2281da177e4SLinus Torvalds 		"inb $0xb2,%%al\n\t"
2291da177e4SLinus Torvalds 		"# fill out the memory with the values in the registers\n\t" \
2301da177e4SLinus Torvalds 		"xchgl %%eax,(%%esp)\n\t"
2311da177e4SLinus Torvalds 		"movl %%ebx,4(%%eax)\n\t" \
2321da177e4SLinus Torvalds 		"movl %%ecx,8(%%eax)\n\t" \
2331da177e4SLinus Torvalds 		"movl %%edx,12(%%eax)\n\t" \
2341da177e4SLinus Torvalds 		"movl %%esi,16(%%eax)\n\t" \
2351da177e4SLinus Torvalds 		"movl %%edi,20(%%eax)\n\t" \
2361da177e4SLinus Torvalds 		"popl %%edx\n\t" \
2371da177e4SLinus Torvalds 		"movl %%edx,0(%%eax)\n\t" \
2381da177e4SLinus Torvalds 		"# setup the return value to the carry flag\n\t" \
2391da177e4SLinus Torvalds 		"lahf\n\t" \
2401da177e4SLinus Torvalds 		"shrl $8,%%eax\n\t" \
2411da177e4SLinus Torvalds 		"andl $1,%%eax\n" \
2421da177e4SLinus Torvalds 		: "=a" (eax)
2431da177e4SLinus Torvalds 		: "a" (regs)
2441da177e4SLinus Torvalds 		: "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
2451da177e4SLinus Torvalds 
2461da177e4SLinus Torvalds 	return eax;
2471da177e4SLinus Torvalds }
2481da177e4SLinus Torvalds 
2491da177e4SLinus Torvalds 
2501da177e4SLinus Torvalds static int tosh_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
2511da177e4SLinus Torvalds 	unsigned long arg)
2521da177e4SLinus Torvalds {
2531da177e4SLinus Torvalds 	SMMRegisters regs;
2541da177e4SLinus Torvalds 	SMMRegisters __user *argp = (SMMRegisters __user *)arg;
2551da177e4SLinus Torvalds 	unsigned short ax,bx;
2561da177e4SLinus Torvalds 	int err;
2571da177e4SLinus Torvalds 
2581da177e4SLinus Torvalds 	if (!argp)
2591da177e4SLinus Torvalds 		return -EINVAL;
2601da177e4SLinus Torvalds 
2611da177e4SLinus Torvalds 	if (copy_from_user(&regs, argp, sizeof(SMMRegisters)))
2621da177e4SLinus Torvalds 		return -EFAULT;
2631da177e4SLinus Torvalds 
2641da177e4SLinus Torvalds 	switch (cmd) {
2651da177e4SLinus Torvalds 		case TOSH_SMM:
2661da177e4SLinus Torvalds 			ax = regs.eax & 0xff00;
2671da177e4SLinus Torvalds 			bx = regs.ebx & 0xffff;
2681da177e4SLinus Torvalds 			/* block HCI calls to read/write memory & PCI devices */
2691da177e4SLinus Torvalds 			if (((ax==0xff00) || (ax==0xfe00)) && (bx>0x0069))
2701da177e4SLinus Torvalds 				return -EINVAL;
2711da177e4SLinus Torvalds 
2721da177e4SLinus Torvalds 			/* do we need to emulate the fan ? */
2731da177e4SLinus Torvalds 			if (tosh_fan==1) {
2741da177e4SLinus Torvalds 				if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) {
2751da177e4SLinus Torvalds 					err = tosh_emulate_fan(&regs);
2761da177e4SLinus Torvalds 					break;
2771da177e4SLinus Torvalds 				}
2781da177e4SLinus Torvalds 			}
2791da177e4SLinus Torvalds 			err = tosh_smm(&regs);
2801da177e4SLinus Torvalds 			break;
2811da177e4SLinus Torvalds 		default:
2821da177e4SLinus Torvalds 			return -EINVAL;
2831da177e4SLinus Torvalds 	}
2841da177e4SLinus Torvalds 
2851da177e4SLinus Torvalds         if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))
2861da177e4SLinus Torvalds         	return -EFAULT;
2871da177e4SLinus Torvalds 
2881da177e4SLinus Torvalds 	return (err==0) ? 0:-EINVAL;
2891da177e4SLinus Torvalds }
2901da177e4SLinus Torvalds 
2911da177e4SLinus Torvalds 
2921da177e4SLinus Torvalds /*
2931da177e4SLinus Torvalds  * Print the information for /proc/toshiba
2941da177e4SLinus Torvalds  */
2951da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
2961da177e4SLinus Torvalds static int tosh_get_info(char *buffer, char **start, off_t fpos, int length)
2971da177e4SLinus Torvalds {
2981da177e4SLinus Torvalds 	char *temp;
2991da177e4SLinus Torvalds 	int key;
3001da177e4SLinus Torvalds 
3011da177e4SLinus Torvalds 	temp = buffer;
3021da177e4SLinus Torvalds 	key = tosh_fn_status();
3031da177e4SLinus Torvalds 
3041da177e4SLinus Torvalds 	/* Arguments
3051da177e4SLinus Torvalds 	     0) Linux driver version (this will change if format changes)
3061da177e4SLinus Torvalds 	     1) Machine ID
3071da177e4SLinus Torvalds 	     2) SCI version
3081da177e4SLinus Torvalds 	     3) BIOS version (major, minor)
3091da177e4SLinus Torvalds 	     4) BIOS date (in SCI date format)
3101da177e4SLinus Torvalds 	     5) Fn Key status
3111da177e4SLinus Torvalds 	*/
3121da177e4SLinus Torvalds 
3131da177e4SLinus Torvalds 	temp += sprintf(temp, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n",
3141da177e4SLinus Torvalds 		tosh_id,
3151da177e4SLinus Torvalds 		(tosh_sci & 0xff00)>>8,
3161da177e4SLinus Torvalds 		tosh_sci & 0xff,
3171da177e4SLinus Torvalds 		(tosh_bios & 0xff00)>>8,
3181da177e4SLinus Torvalds 		tosh_bios & 0xff,
3191da177e4SLinus Torvalds 		tosh_date,
3201da177e4SLinus Torvalds 		key);
3211da177e4SLinus Torvalds 
3221da177e4SLinus Torvalds 	return temp-buffer;
3231da177e4SLinus Torvalds }
3241da177e4SLinus Torvalds #endif
3251da177e4SLinus Torvalds 
3261da177e4SLinus Torvalds 
3271da177e4SLinus Torvalds /*
3281da177e4SLinus Torvalds  * Determine which port to use for the Fn key status
3291da177e4SLinus Torvalds  */
3301da177e4SLinus Torvalds static void tosh_set_fn_port(void)
3311da177e4SLinus Torvalds {
3321da177e4SLinus Torvalds 	switch (tosh_id) {
3331da177e4SLinus Torvalds 		case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10:
3341da177e4SLinus Torvalds 		case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b:
3351da177e4SLinus Torvalds 		case 0xfc5a:
3361da177e4SLinus Torvalds 			tosh_fn = 0x62;
3371da177e4SLinus Torvalds 			break;
3381da177e4SLinus Torvalds 		case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0:
3391da177e4SLinus Torvalds 		case 0xfce2:
3401da177e4SLinus Torvalds 			tosh_fn = 0x68;
3411da177e4SLinus Torvalds 			break;
3421da177e4SLinus Torvalds 		default:
3431da177e4SLinus Torvalds 			tosh_fn = 0x00;
3441da177e4SLinus Torvalds 			break;
3451da177e4SLinus Torvalds 	}
3461da177e4SLinus Torvalds 
3471da177e4SLinus Torvalds 	return;
3481da177e4SLinus Torvalds }
3491da177e4SLinus Torvalds 
3501da177e4SLinus Torvalds 
3511da177e4SLinus Torvalds /*
3521da177e4SLinus Torvalds  * Get the machine identification number of the current model
3531da177e4SLinus Torvalds  */
3541da177e4SLinus Torvalds static int tosh_get_machine_id(void)
3551da177e4SLinus Torvalds {
3561da177e4SLinus Torvalds 	int id;
3571da177e4SLinus Torvalds 	SMMRegisters regs;
3581da177e4SLinus Torvalds 	unsigned short bx,cx;
3591da177e4SLinus Torvalds 	unsigned long address;
3601da177e4SLinus Torvalds 
3611da177e4SLinus Torvalds 	id = (0x100*(int) isa_readb(0xffffe))+((int) isa_readb(0xffffa));
3621da177e4SLinus Torvalds 
3631da177e4SLinus Torvalds 	/* do we have a SCTTable machine identication number on our hands */
3641da177e4SLinus Torvalds 
3651da177e4SLinus Torvalds 	if (id==0xfc2f) {
3661da177e4SLinus Torvalds 
3671da177e4SLinus Torvalds 		/* start by getting a pointer into the BIOS */
3681da177e4SLinus Torvalds 
3691da177e4SLinus Torvalds 		regs.eax = 0xc000;
3701da177e4SLinus Torvalds 		regs.ebx = 0x0000;
3711da177e4SLinus Torvalds 		regs.ecx = 0x0000;
3721da177e4SLinus Torvalds 		tosh_smm(&regs);
3731da177e4SLinus Torvalds 		bx = (unsigned short) (regs.ebx & 0xffff);
3741da177e4SLinus Torvalds 
3751da177e4SLinus Torvalds 		/* At this point in the Toshiba routines under MS Windows
3761da177e4SLinus Torvalds 		   the bx register holds 0xe6f5. However my code is producing
3771da177e4SLinus Torvalds 		   a different value! For the time being I will just fudge the
3781da177e4SLinus Torvalds 		   value. This has been verified on a Satellite Pro 430CDT,
3791da177e4SLinus Torvalds 		   Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */
3801da177e4SLinus Torvalds #if TOSH_DEBUG
3811da177e4SLinus Torvalds 		printk("toshiba: debugging ID ebx=0x%04x\n", regs.ebx);
3821da177e4SLinus Torvalds #endif
3831da177e4SLinus Torvalds 		bx = 0xe6f5;
3841da177e4SLinus Torvalds 
3851da177e4SLinus Torvalds 		/* now twiddle with our pointer a bit */
3861da177e4SLinus Torvalds 
3871da177e4SLinus Torvalds 		address = 0x000f0000+bx;
3881da177e4SLinus Torvalds 		cx = isa_readw(address);
3891da177e4SLinus Torvalds 		address = 0x000f0009+bx+cx;
3901da177e4SLinus Torvalds 		cx = isa_readw(address);
3911da177e4SLinus Torvalds 		address = 0x000f000a+cx;
3921da177e4SLinus Torvalds 		cx = isa_readw(address);
3931da177e4SLinus Torvalds 
3941da177e4SLinus Torvalds 		/* now construct our machine identification number */
3951da177e4SLinus Torvalds 
3961da177e4SLinus Torvalds 		id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8);
3971da177e4SLinus Torvalds 	}
3981da177e4SLinus Torvalds 
3991da177e4SLinus Torvalds 	return id;
4001da177e4SLinus Torvalds }
4011da177e4SLinus Torvalds 
4021da177e4SLinus Torvalds 
4031da177e4SLinus Torvalds /*
4041da177e4SLinus Torvalds  * Probe for the presence of a Toshiba laptop
4051da177e4SLinus Torvalds  *
4061da177e4SLinus Torvalds  *   returns and non-zero if unable to detect the presence of a Toshiba
4071da177e4SLinus Torvalds  *   laptop, otherwise zero and determines the Machine ID, BIOS version and
4081da177e4SLinus Torvalds  *   date, and SCI version.
4091da177e4SLinus Torvalds  */
4101da177e4SLinus Torvalds static int tosh_probe(void)
4111da177e4SLinus Torvalds {
4121da177e4SLinus Torvalds 	int i,major,minor,day,year,month,flag;
4131da177e4SLinus Torvalds 	unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 };
4141da177e4SLinus Torvalds 	SMMRegisters regs;
4151da177e4SLinus Torvalds 
4161da177e4SLinus Torvalds 	/* extra sanity check for the string "TOSHIBA" in the BIOS because
4171da177e4SLinus Torvalds 	   some machines that are not Toshiba's pass the next test */
4181da177e4SLinus Torvalds 
4191da177e4SLinus Torvalds 	for (i=0;i<7;i++) {
4201da177e4SLinus Torvalds 		if (isa_readb(0xfe010+i)!=signature[i]) {
4211da177e4SLinus Torvalds 			printk("toshiba: not a supported Toshiba laptop\n");
4221da177e4SLinus Torvalds 			return -ENODEV;
4231da177e4SLinus Torvalds 		}
4241da177e4SLinus Torvalds 	}
4251da177e4SLinus Torvalds 
4261da177e4SLinus Torvalds 	/* call the Toshiba SCI support check routine */
4271da177e4SLinus Torvalds 
4281da177e4SLinus Torvalds 	regs.eax = 0xf0f0;
4291da177e4SLinus Torvalds 	regs.ebx = 0x0000;
4301da177e4SLinus Torvalds 	regs.ecx = 0x0000;
4311da177e4SLinus Torvalds 	flag = tosh_smm(&regs);
4321da177e4SLinus Torvalds 
4331da177e4SLinus Torvalds 	/* if this is not a Toshiba laptop carry flag is set and ah=0x86 */
4341da177e4SLinus Torvalds 
4351da177e4SLinus Torvalds 	if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) {
4361da177e4SLinus Torvalds 		printk("toshiba: not a supported Toshiba laptop\n");
4371da177e4SLinus Torvalds 		return -ENODEV;
4381da177e4SLinus Torvalds 	}
4391da177e4SLinus Torvalds 
4401da177e4SLinus Torvalds 	/* if we get this far then we are running on a Toshiba (probably)! */
4411da177e4SLinus Torvalds 
4421da177e4SLinus Torvalds 	tosh_sci = regs.edx & 0xffff;
4431da177e4SLinus Torvalds 
4441da177e4SLinus Torvalds 	/* next get the machine ID of the current laptop */
4451da177e4SLinus Torvalds 
4461da177e4SLinus Torvalds 	tosh_id = tosh_get_machine_id();
4471da177e4SLinus Torvalds 
4481da177e4SLinus Torvalds 	/* get the BIOS version */
4491da177e4SLinus Torvalds 
4501da177e4SLinus Torvalds 	major = isa_readb(0xfe009)-'0';
4511da177e4SLinus Torvalds 	minor = ((isa_readb(0xfe00b)-'0')*10)+(isa_readb(0xfe00c)-'0');
4521da177e4SLinus Torvalds 	tosh_bios = (major*0x100)+minor;
4531da177e4SLinus Torvalds 
4541da177e4SLinus Torvalds 	/* get the BIOS date */
4551da177e4SLinus Torvalds 
4561da177e4SLinus Torvalds 	day = ((isa_readb(0xffff5)-'0')*10)+(isa_readb(0xffff6)-'0');
4571da177e4SLinus Torvalds 	month = ((isa_readb(0xffff8)-'0')*10)+(isa_readb(0xffff9)-'0');
4581da177e4SLinus Torvalds 	year = ((isa_readb(0xffffb)-'0')*10)+(isa_readb(0xffffc)-'0');
4591da177e4SLinus Torvalds 	tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6)
4601da177e4SLinus Torvalds 		| ((day & 0x1f)<<1);
4611da177e4SLinus Torvalds 
4621da177e4SLinus Torvalds 
4631da177e4SLinus Torvalds 	/* in theory we should check the ports we are going to use for the
4641da177e4SLinus Torvalds 	   fn key detection (and the fan on the Portage 610/Tecra700), and
4651da177e4SLinus Torvalds 	   then request them to stop other drivers using them. However as
4661da177e4SLinus Torvalds 	   the keyboard driver grabs 0x60-0x6f and the pic driver grabs
4671da177e4SLinus Torvalds 	   0xa0-0xbf we can't. We just have to live dangerously and use the
4681da177e4SLinus Torvalds 	   ports anyway, oh boy! */
4691da177e4SLinus Torvalds 
4701da177e4SLinus Torvalds 	/* do we need to emulate the fan? */
4711da177e4SLinus Torvalds 
4721da177e4SLinus Torvalds 	if ((tosh_id==0xfccb) || (tosh_id==0xfccc))
4731da177e4SLinus Torvalds 		tosh_fan = 1;
4741da177e4SLinus Torvalds 
4751da177e4SLinus Torvalds 	return 0;
4761da177e4SLinus Torvalds }
4771da177e4SLinus Torvalds 
4781da177e4SLinus Torvalds int __init tosh_init(void)
4791da177e4SLinus Torvalds {
4801da177e4SLinus Torvalds 	int retval;
4811da177e4SLinus Torvalds 	/* are we running on a Toshiba laptop */
4821da177e4SLinus Torvalds 
4831da177e4SLinus Torvalds 	if (tosh_probe()!=0)
4841da177e4SLinus Torvalds 		return -EIO;
4851da177e4SLinus Torvalds 
4861da177e4SLinus Torvalds 	printk(KERN_INFO "Toshiba System Managment Mode driver v"
4871da177e4SLinus Torvalds 		TOSH_VERSION"\n");
4881da177e4SLinus Torvalds 
4891da177e4SLinus Torvalds 	/* set the port to use for Fn status if not specified as a parameter */
4901da177e4SLinus Torvalds 	if (tosh_fn==0x00)
4911da177e4SLinus Torvalds 		tosh_set_fn_port();
4921da177e4SLinus Torvalds 
4931da177e4SLinus Torvalds 	/* register the device file */
4941da177e4SLinus Torvalds 	retval = misc_register(&tosh_device);
4951da177e4SLinus Torvalds 	if(retval < 0)
4961da177e4SLinus Torvalds 		return retval;
4971da177e4SLinus Torvalds 
4981da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
4991da177e4SLinus Torvalds 	/* register the proc entry */
5001da177e4SLinus Torvalds 	if(create_proc_info_entry("toshiba", 0, NULL, tosh_get_info) == NULL){
5011da177e4SLinus Torvalds 		misc_deregister(&tosh_device);
5021da177e4SLinus Torvalds 		return -ENOMEM;
5031da177e4SLinus Torvalds 	}
5041da177e4SLinus Torvalds #endif
5051da177e4SLinus Torvalds 
5061da177e4SLinus Torvalds 	return 0;
5071da177e4SLinus Torvalds }
5081da177e4SLinus Torvalds 
5091da177e4SLinus Torvalds #ifdef MODULE
5101da177e4SLinus Torvalds int init_module(void)
5111da177e4SLinus Torvalds {
5121da177e4SLinus Torvalds 	return tosh_init();
5131da177e4SLinus Torvalds }
5141da177e4SLinus Torvalds 
5151da177e4SLinus Torvalds void cleanup_module(void)
5161da177e4SLinus Torvalds {
5171da177e4SLinus Torvalds 	/* remove the proc entry */
5181da177e4SLinus Torvalds 
5191da177e4SLinus Torvalds 	remove_proc_entry("toshiba", NULL);
5201da177e4SLinus Torvalds 
5211da177e4SLinus Torvalds 	/* unregister the device file */
5221da177e4SLinus Torvalds 
5231da177e4SLinus Torvalds 	misc_deregister(&tosh_device);
5241da177e4SLinus Torvalds }
5251da177e4SLinus Torvalds #endif
5261da177e4SLinus Torvalds 
5271da177e4SLinus Torvalds MODULE_LICENSE("GPL");
5281da177e4SLinus Torvalds MODULE_PARM_DESC(tosh_fn, "User specified Fn key detection port");
5291da177e4SLinus Torvalds MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>");
5301da177e4SLinus Torvalds MODULE_DESCRIPTION("Toshiba laptop SMM driver");
5311da177e4SLinus Torvalds MODULE_SUPPORTED_DEVICE("toshiba");
5321da177e4SLinus Torvalds 
533