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/types.h> 621da177e4SLinus Torvalds #include <linux/fcntl.h> 631da177e4SLinus Torvalds #include <linux/miscdevice.h> 641da177e4SLinus Torvalds #include <linux/ioport.h> 651da177e4SLinus Torvalds #include <asm/io.h> 661da177e4SLinus Torvalds #include <asm/uaccess.h> 671da177e4SLinus Torvalds #include <linux/init.h> 681da177e4SLinus Torvalds #include <linux/stat.h> 691da177e4SLinus Torvalds #include <linux/proc_fs.h> 70967bb77cSAlexey Dobriyan #include <linux/seq_file.h> 71613655faSArnd Bergmann #include <linux/mutex.h> 721da177e4SLinus Torvalds #include <linux/toshiba.h> 731da177e4SLinus Torvalds 741da177e4SLinus Torvalds #define TOSH_MINOR_DEV 181 751da177e4SLinus Torvalds 763f5f7e2eSDmitry Torokhov MODULE_LICENSE("GPL"); 773f5f7e2eSDmitry Torokhov MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>"); 783f5f7e2eSDmitry Torokhov MODULE_DESCRIPTION("Toshiba laptop SMM driver"); 793f5f7e2eSDmitry Torokhov MODULE_SUPPORTED_DEVICE("toshiba"); 801da177e4SLinus Torvalds 81613655faSArnd Bergmann static DEFINE_MUTEX(tosh_mutex); 823f5f7e2eSDmitry Torokhov static int tosh_fn; 833f5f7e2eSDmitry Torokhov module_param_named(fn, tosh_fn, int, 0); 843f5f7e2eSDmitry Torokhov MODULE_PARM_DESC(fn, "User specified Fn key detection port"); 851da177e4SLinus Torvalds 863f5f7e2eSDmitry Torokhov static int tosh_id; 873f5f7e2eSDmitry Torokhov static int tosh_bios; 883f5f7e2eSDmitry Torokhov static int tosh_date; 893f5f7e2eSDmitry Torokhov static int tosh_sci; 903f5f7e2eSDmitry Torokhov static int tosh_fan; 911da177e4SLinus Torvalds 923e8d95d9SAlan Cox static long tosh_ioctl(struct file *, unsigned int, 931da177e4SLinus Torvalds unsigned long); 941da177e4SLinus Torvalds 951da177e4SLinus Torvalds 9662322d25SArjan van de Ven static const struct file_operations tosh_fops = { 971da177e4SLinus Torvalds .owner = THIS_MODULE, 983e8d95d9SAlan Cox .unlocked_ioctl = tosh_ioctl, 996038f373SArnd Bergmann .llseek = noop_llseek, 1001da177e4SLinus Torvalds }; 1011da177e4SLinus Torvalds 1021da177e4SLinus Torvalds static struct miscdevice tosh_device = { 1031da177e4SLinus Torvalds TOSH_MINOR_DEV, 1041da177e4SLinus Torvalds "toshiba", 1051da177e4SLinus Torvalds &tosh_fops 1061da177e4SLinus Torvalds }; 1071da177e4SLinus Torvalds 1081da177e4SLinus Torvalds /* 1091da177e4SLinus Torvalds * Read the Fn key status 1101da177e4SLinus Torvalds */ 1111da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS 1121da177e4SLinus Torvalds static int tosh_fn_status(void) 1131da177e4SLinus Torvalds { 1141da177e4SLinus Torvalds unsigned char scan; 1151da177e4SLinus Torvalds unsigned long flags; 1161da177e4SLinus Torvalds 1171da177e4SLinus Torvalds if (tosh_fn!=0) { 1181da177e4SLinus Torvalds scan = inb(tosh_fn); 1191da177e4SLinus Torvalds } else { 1201da177e4SLinus Torvalds local_irq_save(flags); 1211da177e4SLinus Torvalds outb(0x8e, 0xe4); 1221da177e4SLinus Torvalds scan = inb(0xe5); 1231da177e4SLinus Torvalds local_irq_restore(flags); 1241da177e4SLinus Torvalds } 1251da177e4SLinus Torvalds 1261da177e4SLinus Torvalds return (int) scan; 1271da177e4SLinus Torvalds } 1281da177e4SLinus Torvalds #endif 1291da177e4SLinus Torvalds 1301da177e4SLinus Torvalds 1311da177e4SLinus Torvalds /* 1321da177e4SLinus Torvalds * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function 1331da177e4SLinus Torvalds */ 1341da177e4SLinus Torvalds static int tosh_emulate_fan(SMMRegisters *regs) 1351da177e4SLinus Torvalds { 1361da177e4SLinus Torvalds unsigned long eax,ecx,flags; 1371da177e4SLinus Torvalds unsigned char al; 1381da177e4SLinus Torvalds 1391da177e4SLinus Torvalds eax = regs->eax & 0xff00; 1401da177e4SLinus Torvalds ecx = regs->ecx & 0xffff; 1411da177e4SLinus Torvalds 1421da177e4SLinus Torvalds /* Portage 610CT */ 1431da177e4SLinus Torvalds 1441da177e4SLinus Torvalds if (tosh_id==0xfccb) { 1451da177e4SLinus Torvalds if (eax==0xfe00) { 1461da177e4SLinus Torvalds /* fan status */ 1471da177e4SLinus Torvalds local_irq_save(flags); 1481da177e4SLinus Torvalds outb(0xbe, 0xe4); 1491da177e4SLinus Torvalds al = inb(0xe5); 1501da177e4SLinus Torvalds local_irq_restore(flags); 1511da177e4SLinus Torvalds regs->eax = 0x00; 1521da177e4SLinus Torvalds regs->ecx = (unsigned int) (al & 0x01); 1531da177e4SLinus Torvalds } 1541da177e4SLinus Torvalds if ((eax==0xff00) && (ecx==0x0000)) { 1551da177e4SLinus Torvalds /* fan off */ 1561da177e4SLinus Torvalds local_irq_save(flags); 1571da177e4SLinus Torvalds outb(0xbe, 0xe4); 1581da177e4SLinus Torvalds al = inb(0xe5); 1591da177e4SLinus Torvalds outb(0xbe, 0xe4); 1601da177e4SLinus Torvalds outb (al | 0x01, 0xe5); 1611da177e4SLinus Torvalds local_irq_restore(flags); 1621da177e4SLinus Torvalds regs->eax = 0x00; 1631da177e4SLinus Torvalds regs->ecx = 0x00; 1641da177e4SLinus Torvalds } 1651da177e4SLinus Torvalds if ((eax==0xff00) && (ecx==0x0001)) { 1661da177e4SLinus Torvalds /* fan on */ 1671da177e4SLinus Torvalds local_irq_save(flags); 1681da177e4SLinus Torvalds outb(0xbe, 0xe4); 1691da177e4SLinus Torvalds al = inb(0xe5); 1701da177e4SLinus Torvalds outb(0xbe, 0xe4); 1711da177e4SLinus Torvalds outb(al & 0xfe, 0xe5); 1721da177e4SLinus Torvalds local_irq_restore(flags); 1731da177e4SLinus Torvalds regs->eax = 0x00; 1741da177e4SLinus Torvalds regs->ecx = 0x01; 1751da177e4SLinus Torvalds } 1761da177e4SLinus Torvalds } 1771da177e4SLinus Torvalds 1781da177e4SLinus Torvalds /* Tecra 700CS/CDT */ 1791da177e4SLinus Torvalds 1801da177e4SLinus Torvalds if (tosh_id==0xfccc) { 1811da177e4SLinus Torvalds if (eax==0xfe00) { 1821da177e4SLinus Torvalds /* fan status */ 1831da177e4SLinus Torvalds local_irq_save(flags); 1841da177e4SLinus Torvalds outb(0xe0, 0xe4); 1851da177e4SLinus Torvalds al = inb(0xe5); 1861da177e4SLinus Torvalds local_irq_restore(flags); 1871da177e4SLinus Torvalds regs->eax = 0x00; 1881da177e4SLinus Torvalds regs->ecx = al & 0x01; 1891da177e4SLinus Torvalds } 1901da177e4SLinus Torvalds if ((eax==0xff00) && (ecx==0x0000)) { 1911da177e4SLinus Torvalds /* fan off */ 1921da177e4SLinus Torvalds local_irq_save(flags); 1931da177e4SLinus Torvalds outb(0xe0, 0xe4); 1941da177e4SLinus Torvalds al = inb(0xe5); 1951da177e4SLinus Torvalds outw(0xe0 | ((al & 0xfe) << 8), 0xe4); 1961da177e4SLinus Torvalds local_irq_restore(flags); 1971da177e4SLinus Torvalds regs->eax = 0x00; 1981da177e4SLinus Torvalds regs->ecx = 0x00; 1991da177e4SLinus Torvalds } 2001da177e4SLinus Torvalds if ((eax==0xff00) && (ecx==0x0001)) { 2011da177e4SLinus Torvalds /* fan on */ 2021da177e4SLinus Torvalds local_irq_save(flags); 2031da177e4SLinus Torvalds outb(0xe0, 0xe4); 2041da177e4SLinus Torvalds al = inb(0xe5); 2051da177e4SLinus Torvalds outw(0xe0 | ((al | 0x01) << 8), 0xe4); 2061da177e4SLinus Torvalds local_irq_restore(flags); 2071da177e4SLinus Torvalds regs->eax = 0x00; 2081da177e4SLinus Torvalds regs->ecx = 0x01; 2091da177e4SLinus Torvalds } 2101da177e4SLinus Torvalds } 2111da177e4SLinus Torvalds 2121da177e4SLinus Torvalds return 0; 2131da177e4SLinus Torvalds } 2141da177e4SLinus Torvalds 2151da177e4SLinus Torvalds 2161da177e4SLinus Torvalds /* 2171da177e4SLinus Torvalds * Put the laptop into System Management Mode 2181da177e4SLinus Torvalds */ 2191da177e4SLinus Torvalds int tosh_smm(SMMRegisters *regs) 2201da177e4SLinus Torvalds { 2211da177e4SLinus Torvalds int eax; 2221da177e4SLinus Torvalds 2231da177e4SLinus Torvalds asm ("# load the values into the registers\n\t" \ 2241da177e4SLinus Torvalds "pushl %%eax\n\t" \ 2251da177e4SLinus Torvalds "movl 0(%%eax),%%edx\n\t" \ 2261da177e4SLinus Torvalds "push %%edx\n\t" \ 2271da177e4SLinus Torvalds "movl 4(%%eax),%%ebx\n\t" \ 2281da177e4SLinus Torvalds "movl 8(%%eax),%%ecx\n\t" \ 2291da177e4SLinus Torvalds "movl 12(%%eax),%%edx\n\t" \ 2301da177e4SLinus Torvalds "movl 16(%%eax),%%esi\n\t" \ 2311da177e4SLinus Torvalds "movl 20(%%eax),%%edi\n\t" \ 2321da177e4SLinus Torvalds "popl %%eax\n\t" \ 2331da177e4SLinus Torvalds "# call the System Management mode\n\t" \ 2341da177e4SLinus Torvalds "inb $0xb2,%%al\n\t" 2351da177e4SLinus Torvalds "# fill out the memory with the values in the registers\n\t" \ 2361da177e4SLinus Torvalds "xchgl %%eax,(%%esp)\n\t" 2371da177e4SLinus Torvalds "movl %%ebx,4(%%eax)\n\t" \ 2381da177e4SLinus Torvalds "movl %%ecx,8(%%eax)\n\t" \ 2391da177e4SLinus Torvalds "movl %%edx,12(%%eax)\n\t" \ 2401da177e4SLinus Torvalds "movl %%esi,16(%%eax)\n\t" \ 2411da177e4SLinus Torvalds "movl %%edi,20(%%eax)\n\t" \ 2421da177e4SLinus Torvalds "popl %%edx\n\t" \ 2431da177e4SLinus Torvalds "movl %%edx,0(%%eax)\n\t" \ 2441da177e4SLinus Torvalds "# setup the return value to the carry flag\n\t" \ 2451da177e4SLinus Torvalds "lahf\n\t" \ 2461da177e4SLinus Torvalds "shrl $8,%%eax\n\t" \ 2471da177e4SLinus Torvalds "andl $1,%%eax\n" \ 2481da177e4SLinus Torvalds : "=a" (eax) 2491da177e4SLinus Torvalds : "a" (regs) 2501da177e4SLinus Torvalds : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); 2511da177e4SLinus Torvalds 2521da177e4SLinus Torvalds return eax; 2531da177e4SLinus Torvalds } 2540bf93226SRandy Dunlap EXPORT_SYMBOL(tosh_smm); 2551da177e4SLinus Torvalds 2561da177e4SLinus Torvalds 2573e8d95d9SAlan Cox static long tosh_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) 2581da177e4SLinus Torvalds { 2591da177e4SLinus Torvalds SMMRegisters regs; 2601da177e4SLinus Torvalds SMMRegisters __user *argp = (SMMRegisters __user *)arg; 2611da177e4SLinus Torvalds unsigned short ax,bx; 2621da177e4SLinus Torvalds int err; 2631da177e4SLinus Torvalds 2641da177e4SLinus Torvalds if (!argp) 2651da177e4SLinus Torvalds return -EINVAL; 2661da177e4SLinus Torvalds 2671da177e4SLinus Torvalds if (copy_from_user(®s, argp, sizeof(SMMRegisters))) 2681da177e4SLinus Torvalds return -EFAULT; 2691da177e4SLinus Torvalds 2701da177e4SLinus Torvalds switch (cmd) { 2711da177e4SLinus Torvalds case TOSH_SMM: 2721da177e4SLinus Torvalds ax = regs.eax & 0xff00; 2731da177e4SLinus Torvalds bx = regs.ebx & 0xffff; 2741da177e4SLinus Torvalds /* block HCI calls to read/write memory & PCI devices */ 2751da177e4SLinus Torvalds if (((ax==0xff00) || (ax==0xfe00)) && (bx>0x0069)) 2761da177e4SLinus Torvalds return -EINVAL; 2771da177e4SLinus Torvalds 2781da177e4SLinus Torvalds /* do we need to emulate the fan ? */ 279613655faSArnd Bergmann mutex_lock(&tosh_mutex); 2801da177e4SLinus Torvalds if (tosh_fan==1) { 2811da177e4SLinus Torvalds if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) { 2821da177e4SLinus Torvalds err = tosh_emulate_fan(®s); 283613655faSArnd Bergmann mutex_unlock(&tosh_mutex); 2841da177e4SLinus Torvalds break; 2851da177e4SLinus Torvalds } 2861da177e4SLinus Torvalds } 2871da177e4SLinus Torvalds err = tosh_smm(®s); 288613655faSArnd Bergmann mutex_unlock(&tosh_mutex); 2891da177e4SLinus Torvalds break; 2901da177e4SLinus Torvalds default: 2911da177e4SLinus Torvalds return -EINVAL; 2921da177e4SLinus Torvalds } 2931da177e4SLinus Torvalds 2941da177e4SLinus Torvalds if (copy_to_user(argp, ®s, sizeof(SMMRegisters))) 2951da177e4SLinus Torvalds return -EFAULT; 2961da177e4SLinus Torvalds 2971da177e4SLinus Torvalds return (err==0) ? 0:-EINVAL; 2981da177e4SLinus Torvalds } 2991da177e4SLinus Torvalds 3001da177e4SLinus Torvalds 3011da177e4SLinus Torvalds /* 3021da177e4SLinus Torvalds * Print the information for /proc/toshiba 3031da177e4SLinus Torvalds */ 3041da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS 305967bb77cSAlexey Dobriyan static int proc_toshiba_show(struct seq_file *m, void *v) 3061da177e4SLinus Torvalds { 3071da177e4SLinus Torvalds int key; 3081da177e4SLinus Torvalds 3091da177e4SLinus Torvalds key = tosh_fn_status(); 3101da177e4SLinus Torvalds 3111da177e4SLinus Torvalds /* Arguments 3121da177e4SLinus Torvalds 0) Linux driver version (this will change if format changes) 3131da177e4SLinus Torvalds 1) Machine ID 3141da177e4SLinus Torvalds 2) SCI version 3151da177e4SLinus Torvalds 3) BIOS version (major, minor) 3161da177e4SLinus Torvalds 4) BIOS date (in SCI date format) 3171da177e4SLinus Torvalds 5) Fn Key status 3181da177e4SLinus Torvalds */ 319967bb77cSAlexey Dobriyan seq_printf(m, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n", 3201da177e4SLinus Torvalds tosh_id, 3211da177e4SLinus Torvalds (tosh_sci & 0xff00)>>8, 3221da177e4SLinus Torvalds tosh_sci & 0xff, 3231da177e4SLinus Torvalds (tosh_bios & 0xff00)>>8, 3241da177e4SLinus Torvalds tosh_bios & 0xff, 3251da177e4SLinus Torvalds tosh_date, 3261da177e4SLinus Torvalds key); 327967bb77cSAlexey Dobriyan return 0; 3281da177e4SLinus Torvalds } 329967bb77cSAlexey Dobriyan 330967bb77cSAlexey Dobriyan static int proc_toshiba_open(struct inode *inode, struct file *file) 331967bb77cSAlexey Dobriyan { 332967bb77cSAlexey Dobriyan return single_open(file, proc_toshiba_show, NULL); 333967bb77cSAlexey Dobriyan } 334967bb77cSAlexey Dobriyan 335967bb77cSAlexey Dobriyan static const struct file_operations proc_toshiba_fops = { 336967bb77cSAlexey Dobriyan .owner = THIS_MODULE, 337967bb77cSAlexey Dobriyan .open = proc_toshiba_open, 338967bb77cSAlexey Dobriyan .read = seq_read, 339967bb77cSAlexey Dobriyan .llseek = seq_lseek, 340967bb77cSAlexey Dobriyan .release = single_release, 341967bb77cSAlexey Dobriyan }; 3421da177e4SLinus Torvalds #endif 3431da177e4SLinus Torvalds 3441da177e4SLinus Torvalds 3451da177e4SLinus Torvalds /* 3461da177e4SLinus Torvalds * Determine which port to use for the Fn key status 3471da177e4SLinus Torvalds */ 3481da177e4SLinus Torvalds static void tosh_set_fn_port(void) 3491da177e4SLinus Torvalds { 3501da177e4SLinus Torvalds switch (tosh_id) { 3511da177e4SLinus Torvalds case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10: 3521da177e4SLinus Torvalds case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b: 3531da177e4SLinus Torvalds case 0xfc5a: 3541da177e4SLinus Torvalds tosh_fn = 0x62; 3551da177e4SLinus Torvalds break; 3561da177e4SLinus Torvalds case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0: 3571da177e4SLinus Torvalds case 0xfce2: 3581da177e4SLinus Torvalds tosh_fn = 0x68; 3591da177e4SLinus Torvalds break; 3601da177e4SLinus Torvalds default: 3611da177e4SLinus Torvalds tosh_fn = 0x00; 3621da177e4SLinus Torvalds break; 3631da177e4SLinus Torvalds } 3641da177e4SLinus Torvalds 3651da177e4SLinus Torvalds return; 3661da177e4SLinus Torvalds } 3671da177e4SLinus Torvalds 3681da177e4SLinus Torvalds 3691da177e4SLinus Torvalds /* 3701da177e4SLinus Torvalds * Get the machine identification number of the current model 3711da177e4SLinus Torvalds */ 372ef5a4c8bSAl Viro static int tosh_get_machine_id(void __iomem *bios) 3731da177e4SLinus Torvalds { 3741da177e4SLinus Torvalds int id; 3751da177e4SLinus Torvalds SMMRegisters regs; 3761da177e4SLinus Torvalds unsigned short bx,cx; 3771da177e4SLinus Torvalds unsigned long address; 3781da177e4SLinus Torvalds 379ef5a4c8bSAl Viro id = (0x100*(int) readb(bios+0xfffe))+((int) readb(bios+0xfffa)); 3801da177e4SLinus Torvalds 3811da177e4SLinus Torvalds /* do we have a SCTTable machine identication number on our hands */ 3821da177e4SLinus Torvalds 3831da177e4SLinus Torvalds if (id==0xfc2f) { 3841da177e4SLinus Torvalds 3851da177e4SLinus Torvalds /* start by getting a pointer into the BIOS */ 3861da177e4SLinus Torvalds 3871da177e4SLinus Torvalds regs.eax = 0xc000; 3881da177e4SLinus Torvalds regs.ebx = 0x0000; 3891da177e4SLinus Torvalds regs.ecx = 0x0000; 3901da177e4SLinus Torvalds tosh_smm(®s); 3911da177e4SLinus Torvalds bx = (unsigned short) (regs.ebx & 0xffff); 3921da177e4SLinus Torvalds 3931da177e4SLinus Torvalds /* At this point in the Toshiba routines under MS Windows 3941da177e4SLinus Torvalds the bx register holds 0xe6f5. However my code is producing 3951da177e4SLinus Torvalds a different value! For the time being I will just fudge the 3961da177e4SLinus Torvalds value. This has been verified on a Satellite Pro 430CDT, 3971da177e4SLinus Torvalds Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */ 3981da177e4SLinus Torvalds #if TOSH_DEBUG 3991da177e4SLinus Torvalds printk("toshiba: debugging ID ebx=0x%04x\n", regs.ebx); 4001da177e4SLinus Torvalds #endif 4011da177e4SLinus Torvalds bx = 0xe6f5; 4021da177e4SLinus Torvalds 4031da177e4SLinus Torvalds /* now twiddle with our pointer a bit */ 4041da177e4SLinus Torvalds 405ef5a4c8bSAl Viro address = bx; 406ef5a4c8bSAl Viro cx = readw(bios + address); 407ef5a4c8bSAl Viro address = 9+bx+cx; 408ef5a4c8bSAl Viro cx = readw(bios + address); 409ef5a4c8bSAl Viro address = 0xa+cx; 410ef5a4c8bSAl Viro cx = readw(bios + address); 4111da177e4SLinus Torvalds 4121da177e4SLinus Torvalds /* now construct our machine identification number */ 4131da177e4SLinus Torvalds 4141da177e4SLinus Torvalds id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8); 4151da177e4SLinus Torvalds } 4161da177e4SLinus Torvalds 4171da177e4SLinus Torvalds return id; 4181da177e4SLinus Torvalds } 4191da177e4SLinus Torvalds 4201da177e4SLinus Torvalds 4211da177e4SLinus Torvalds /* 4221da177e4SLinus Torvalds * Probe for the presence of a Toshiba laptop 4231da177e4SLinus Torvalds * 4241da177e4SLinus Torvalds * returns and non-zero if unable to detect the presence of a Toshiba 4251da177e4SLinus Torvalds * laptop, otherwise zero and determines the Machine ID, BIOS version and 4261da177e4SLinus Torvalds * date, and SCI version. 4271da177e4SLinus Torvalds */ 4281da177e4SLinus Torvalds static int tosh_probe(void) 4291da177e4SLinus Torvalds { 4301da177e4SLinus Torvalds int i,major,minor,day,year,month,flag; 4311da177e4SLinus Torvalds unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 }; 4321da177e4SLinus Torvalds SMMRegisters regs; 43348166932SDan Williams void __iomem *bios = ioremap(0xf0000, 0x10000); 434ef5a4c8bSAl Viro 435ef5a4c8bSAl Viro if (!bios) 436ef5a4c8bSAl Viro return -ENOMEM; 4371da177e4SLinus Torvalds 4381da177e4SLinus Torvalds /* extra sanity check for the string "TOSHIBA" in the BIOS because 4391da177e4SLinus Torvalds some machines that are not Toshiba's pass the next test */ 4401da177e4SLinus Torvalds 4411da177e4SLinus Torvalds for (i=0;i<7;i++) { 442ef5a4c8bSAl Viro if (readb(bios+0xe010+i)!=signature[i]) { 4431da177e4SLinus Torvalds printk("toshiba: not a supported Toshiba laptop\n"); 444ef5a4c8bSAl Viro iounmap(bios); 4451da177e4SLinus Torvalds return -ENODEV; 4461da177e4SLinus Torvalds } 4471da177e4SLinus Torvalds } 4481da177e4SLinus Torvalds 4491da177e4SLinus Torvalds /* call the Toshiba SCI support check routine */ 4501da177e4SLinus Torvalds 4511da177e4SLinus Torvalds regs.eax = 0xf0f0; 4521da177e4SLinus Torvalds regs.ebx = 0x0000; 4531da177e4SLinus Torvalds regs.ecx = 0x0000; 4541da177e4SLinus Torvalds flag = tosh_smm(®s); 4551da177e4SLinus Torvalds 4561da177e4SLinus Torvalds /* if this is not a Toshiba laptop carry flag is set and ah=0x86 */ 4571da177e4SLinus Torvalds 4581da177e4SLinus Torvalds if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) { 4591da177e4SLinus Torvalds printk("toshiba: not a supported Toshiba laptop\n"); 460ef5a4c8bSAl Viro iounmap(bios); 4611da177e4SLinus Torvalds return -ENODEV; 4621da177e4SLinus Torvalds } 4631da177e4SLinus Torvalds 4641da177e4SLinus Torvalds /* if we get this far then we are running on a Toshiba (probably)! */ 4651da177e4SLinus Torvalds 4661da177e4SLinus Torvalds tosh_sci = regs.edx & 0xffff; 4671da177e4SLinus Torvalds 4681da177e4SLinus Torvalds /* next get the machine ID of the current laptop */ 4691da177e4SLinus Torvalds 470ef5a4c8bSAl Viro tosh_id = tosh_get_machine_id(bios); 4711da177e4SLinus Torvalds 4721da177e4SLinus Torvalds /* get the BIOS version */ 4731da177e4SLinus Torvalds 474ef5a4c8bSAl Viro major = readb(bios+0xe009)-'0'; 475ef5a4c8bSAl Viro minor = ((readb(bios+0xe00b)-'0')*10)+(readb(bios+0xe00c)-'0'); 4761da177e4SLinus Torvalds tosh_bios = (major*0x100)+minor; 4771da177e4SLinus Torvalds 4781da177e4SLinus Torvalds /* get the BIOS date */ 4791da177e4SLinus Torvalds 480ef5a4c8bSAl Viro day = ((readb(bios+0xfff5)-'0')*10)+(readb(bios+0xfff6)-'0'); 481ef5a4c8bSAl Viro month = ((readb(bios+0xfff8)-'0')*10)+(readb(bios+0xfff9)-'0'); 482ef5a4c8bSAl Viro year = ((readb(bios+0xfffb)-'0')*10)+(readb(bios+0xfffc)-'0'); 4831da177e4SLinus Torvalds tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6) 4841da177e4SLinus Torvalds | ((day & 0x1f)<<1); 4851da177e4SLinus Torvalds 4861da177e4SLinus Torvalds 4871da177e4SLinus Torvalds /* in theory we should check the ports we are going to use for the 4881da177e4SLinus Torvalds fn key detection (and the fan on the Portage 610/Tecra700), and 4891da177e4SLinus Torvalds then request them to stop other drivers using them. However as 4901da177e4SLinus Torvalds the keyboard driver grabs 0x60-0x6f and the pic driver grabs 4911da177e4SLinus Torvalds 0xa0-0xbf we can't. We just have to live dangerously and use the 4921da177e4SLinus Torvalds ports anyway, oh boy! */ 4931da177e4SLinus Torvalds 4941da177e4SLinus Torvalds /* do we need to emulate the fan? */ 4951da177e4SLinus Torvalds 4961da177e4SLinus Torvalds if ((tosh_id==0xfccb) || (tosh_id==0xfccc)) 4971da177e4SLinus Torvalds tosh_fan = 1; 4981da177e4SLinus Torvalds 499ef5a4c8bSAl Viro iounmap(bios); 500ef5a4c8bSAl Viro 5011da177e4SLinus Torvalds return 0; 5021da177e4SLinus Torvalds } 5031da177e4SLinus Torvalds 5043f5f7e2eSDmitry Torokhov static int __init toshiba_init(void) 5051da177e4SLinus Torvalds { 5061da177e4SLinus Torvalds int retval; 5071da177e4SLinus Torvalds /* are we running on a Toshiba laptop */ 5081da177e4SLinus Torvalds 5093f5f7e2eSDmitry Torokhov if (tosh_probe()) 5103f5f7e2eSDmitry Torokhov return -ENODEV; 5111da177e4SLinus Torvalds 5128dfba4d7SJoe Perches printk(KERN_INFO "Toshiba System Management Mode driver v" TOSH_VERSION "\n"); 5131da177e4SLinus Torvalds 5141da177e4SLinus Torvalds /* set the port to use for Fn status if not specified as a parameter */ 5151da177e4SLinus Torvalds if (tosh_fn==0x00) 5161da177e4SLinus Torvalds tosh_set_fn_port(); 5171da177e4SLinus Torvalds 5181da177e4SLinus Torvalds /* register the device file */ 5191da177e4SLinus Torvalds retval = misc_register(&tosh_device); 5201da177e4SLinus Torvalds if (retval < 0) 5211da177e4SLinus Torvalds return retval; 5221da177e4SLinus Torvalds 5231da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS 524967bb77cSAlexey Dobriyan { 525967bb77cSAlexey Dobriyan struct proc_dir_entry *pde; 526967bb77cSAlexey Dobriyan 5271b502217SDenis V. Lunev pde = proc_create("toshiba", 0, NULL, &proc_toshiba_fops); 528967bb77cSAlexey Dobriyan if (!pde) { 5291da177e4SLinus Torvalds misc_deregister(&tosh_device); 5301da177e4SLinus Torvalds return -ENOMEM; 5311da177e4SLinus Torvalds } 532967bb77cSAlexey Dobriyan } 5331da177e4SLinus Torvalds #endif 5341da177e4SLinus Torvalds 5351da177e4SLinus Torvalds return 0; 5361da177e4SLinus Torvalds } 5371da177e4SLinus Torvalds 5383f5f7e2eSDmitry Torokhov static void __exit toshiba_exit(void) 5391da177e4SLinus Torvalds { 5401da177e4SLinus Torvalds remove_proc_entry("toshiba", NULL); 5411da177e4SLinus Torvalds misc_deregister(&tosh_device); 5421da177e4SLinus Torvalds } 5431da177e4SLinus Torvalds 5443f5f7e2eSDmitry Torokhov module_init(toshiba_init); 5453f5f7e2eSDmitry Torokhov module_exit(toshiba_exit); 5461da177e4SLinus Torvalds 547