ia64/linux-2.6.18-xen.hg

annotate drivers/macintosh/apm_emu.c @ 893:f994bfe9b93b

linux/blktap2: reduce TLB flush scope

c/s 885 added very coarse TLB flushing. Since these flushes always
follow single page updates, single page flushes (when available) are
sufficient.

Signed-off-by: Jan Beulich <jbeulich@novell.com>
author Keir Fraser <keir.fraser@citrix.com>
date Thu Jun 04 10:32:57 2009 +0100 (2009-06-04)
parents 831230e53067
children
rev   line source
ian@0 1 /* APM emulation layer for PowerMac
ian@0 2 *
ian@0 3 * Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
ian@0 4 *
ian@0 5 * Lots of code inherited from apm.c, see appropriate notice in
ian@0 6 * arch/i386/kernel/apm.c
ian@0 7 *
ian@0 8 * This program is free software; you can redistribute it and/or modify it
ian@0 9 * under the terms of the GNU General Public License as published by the
ian@0 10 * Free Software Foundation; either version 2, or (at your option) any
ian@0 11 * later version.
ian@0 12 *
ian@0 13 * This program is distributed in the hope that it will be useful, but
ian@0 14 * WITHOUT ANY WARRANTY; without even the implied warranty of
ian@0 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
ian@0 16 * General Public License for more details.
ian@0 17 *
ian@0 18 *
ian@0 19 */
ian@0 20
ian@0 21 #include <linux/module.h>
ian@0 22
ian@0 23 #include <linux/poll.h>
ian@0 24 #include <linux/types.h>
ian@0 25 #include <linux/stddef.h>
ian@0 26 #include <linux/timer.h>
ian@0 27 #include <linux/fcntl.h>
ian@0 28 #include <linux/slab.h>
ian@0 29 #include <linux/stat.h>
ian@0 30 #include <linux/proc_fs.h>
ian@0 31 #include <linux/miscdevice.h>
ian@0 32 #include <linux/apm_bios.h>
ian@0 33 #include <linux/init.h>
ian@0 34 #include <linux/sched.h>
ian@0 35 #include <linux/pm.h>
ian@0 36 #include <linux/kernel.h>
ian@0 37 #include <linux/smp_lock.h>
ian@0 38
ian@0 39 #include <linux/adb.h>
ian@0 40 #include <linux/pmu.h>
ian@0 41
ian@0 42 #include <asm/system.h>
ian@0 43 #include <asm/uaccess.h>
ian@0 44 #include <asm/machdep.h>
ian@0 45
ian@0 46 #undef DEBUG
ian@0 47
ian@0 48 #ifdef DEBUG
ian@0 49 #define DBG(args...) printk(KERN_DEBUG args)
ian@0 50 //#define DBG(args...) xmon_printf(args)
ian@0 51 #else
ian@0 52 #define DBG(args...) do { } while (0)
ian@0 53 #endif
ian@0 54
ian@0 55 /*
ian@0 56 * The apm_bios device is one of the misc char devices.
ian@0 57 * This is its minor number.
ian@0 58 */
ian@0 59 #define APM_MINOR_DEV 134
ian@0 60
ian@0 61 /*
ian@0 62 * Maximum number of events stored
ian@0 63 */
ian@0 64 #define APM_MAX_EVENTS 20
ian@0 65
ian@0 66 #define FAKE_APM_BIOS_VERSION 0x0101
ian@0 67
ian@0 68 #define APM_USER_NOTIFY_TIMEOUT (5*HZ)
ian@0 69
ian@0 70 /*
ian@0 71 * The per-file APM data
ian@0 72 */
ian@0 73 struct apm_user {
ian@0 74 int magic;
ian@0 75 struct apm_user * next;
ian@0 76 int suser: 1;
ian@0 77 int suspend_waiting: 1;
ian@0 78 int suspends_pending;
ian@0 79 int suspends_read;
ian@0 80 int event_head;
ian@0 81 int event_tail;
ian@0 82 apm_event_t events[APM_MAX_EVENTS];
ian@0 83 };
ian@0 84
ian@0 85 /*
ian@0 86 * The magic number in apm_user
ian@0 87 */
ian@0 88 #define APM_BIOS_MAGIC 0x4101
ian@0 89
ian@0 90 /*
ian@0 91 * Local variables
ian@0 92 */
ian@0 93 static int suspends_pending;
ian@0 94
ian@0 95 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
ian@0 96 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
ian@0 97 static struct apm_user * user_list;
ian@0 98
ian@0 99 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when);
ian@0 100 static struct pmu_sleep_notifier apm_sleep_notifier = {
ian@0 101 apm_notify_sleep,
ian@0 102 SLEEP_LEVEL_USERLAND,
ian@0 103 };
ian@0 104
ian@0 105 static char driver_version[] = "0.5"; /* no spaces */
ian@0 106
ian@0 107 #ifdef DEBUG
ian@0 108 static char * apm_event_name[] = {
ian@0 109 "system standby",
ian@0 110 "system suspend",
ian@0 111 "normal resume",
ian@0 112 "critical resume",
ian@0 113 "low battery",
ian@0 114 "power status change",
ian@0 115 "update time",
ian@0 116 "critical suspend",
ian@0 117 "user standby",
ian@0 118 "user suspend",
ian@0 119 "system standby resume",
ian@0 120 "capabilities change"
ian@0 121 };
ian@0 122 #define NR_APM_EVENT_NAME \
ian@0 123 (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
ian@0 124
ian@0 125 #endif
ian@0 126
ian@0 127 static int queue_empty(struct apm_user *as)
ian@0 128 {
ian@0 129 return as->event_head == as->event_tail;
ian@0 130 }
ian@0 131
ian@0 132 static apm_event_t get_queued_event(struct apm_user *as)
ian@0 133 {
ian@0 134 as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
ian@0 135 return as->events[as->event_tail];
ian@0 136 }
ian@0 137
ian@0 138 static void queue_event(apm_event_t event, struct apm_user *sender)
ian@0 139 {
ian@0 140 struct apm_user * as;
ian@0 141
ian@0 142 DBG("apm_emu: queue_event(%s)\n", apm_event_name[event-1]);
ian@0 143 if (user_list == NULL)
ian@0 144 return;
ian@0 145 for (as = user_list; as != NULL; as = as->next) {
ian@0 146 if (as == sender)
ian@0 147 continue;
ian@0 148 as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
ian@0 149 if (as->event_head == as->event_tail) {
ian@0 150 static int notified;
ian@0 151
ian@0 152 if (notified++ == 0)
ian@0 153 printk(KERN_ERR "apm_emu: an event queue overflowed\n");
ian@0 154 as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
ian@0 155 }
ian@0 156 as->events[as->event_head] = event;
ian@0 157 if (!as->suser)
ian@0 158 continue;
ian@0 159 switch (event) {
ian@0 160 case APM_SYS_SUSPEND:
ian@0 161 case APM_USER_SUSPEND:
ian@0 162 as->suspends_pending++;
ian@0 163 suspends_pending++;
ian@0 164 break;
ian@0 165 case APM_NORMAL_RESUME:
ian@0 166 as->suspend_waiting = 0;
ian@0 167 break;
ian@0 168 }
ian@0 169 }
ian@0 170 wake_up_interruptible(&apm_waitqueue);
ian@0 171 }
ian@0 172
ian@0 173 static int check_apm_user(struct apm_user *as, const char *func)
ian@0 174 {
ian@0 175 if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
ian@0 176 printk(KERN_ERR "apm_emu: %s passed bad filp\n", func);
ian@0 177 return 1;
ian@0 178 }
ian@0 179 return 0;
ian@0 180 }
ian@0 181
ian@0 182 static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
ian@0 183 {
ian@0 184 struct apm_user * as;
ian@0 185 size_t i;
ian@0 186 apm_event_t event;
ian@0 187 DECLARE_WAITQUEUE(wait, current);
ian@0 188
ian@0 189 as = fp->private_data;
ian@0 190 if (check_apm_user(as, "read"))
ian@0 191 return -EIO;
ian@0 192 if (count < sizeof(apm_event_t))
ian@0 193 return -EINVAL;
ian@0 194 if (queue_empty(as)) {
ian@0 195 if (fp->f_flags & O_NONBLOCK)
ian@0 196 return -EAGAIN;
ian@0 197 add_wait_queue(&apm_waitqueue, &wait);
ian@0 198 repeat:
ian@0 199 set_current_state(TASK_INTERRUPTIBLE);
ian@0 200 if (queue_empty(as) && !signal_pending(current)) {
ian@0 201 schedule();
ian@0 202 goto repeat;
ian@0 203 }
ian@0 204 set_current_state(TASK_RUNNING);
ian@0 205 remove_wait_queue(&apm_waitqueue, &wait);
ian@0 206 }
ian@0 207 i = count;
ian@0 208 while ((i >= sizeof(event)) && !queue_empty(as)) {
ian@0 209 event = get_queued_event(as);
ian@0 210 DBG("apm_emu: do_read, returning: %s\n", apm_event_name[event-1]);
ian@0 211 if (copy_to_user(buf, &event, sizeof(event))) {
ian@0 212 if (i < count)
ian@0 213 break;
ian@0 214 return -EFAULT;
ian@0 215 }
ian@0 216 switch (event) {
ian@0 217 case APM_SYS_SUSPEND:
ian@0 218 case APM_USER_SUSPEND:
ian@0 219 as->suspends_read++;
ian@0 220 break;
ian@0 221 }
ian@0 222 buf += sizeof(event);
ian@0 223 i -= sizeof(event);
ian@0 224 }
ian@0 225 if (i < count)
ian@0 226 return count - i;
ian@0 227 if (signal_pending(current))
ian@0 228 return -ERESTARTSYS;
ian@0 229 return 0;
ian@0 230 }
ian@0 231
ian@0 232 static unsigned int do_poll(struct file *fp, poll_table * wait)
ian@0 233 {
ian@0 234 struct apm_user * as;
ian@0 235
ian@0 236 as = fp->private_data;
ian@0 237 if (check_apm_user(as, "poll"))
ian@0 238 return 0;
ian@0 239 poll_wait(fp, &apm_waitqueue, wait);
ian@0 240 if (!queue_empty(as))
ian@0 241 return POLLIN | POLLRDNORM;
ian@0 242 return 0;
ian@0 243 }
ian@0 244
ian@0 245 static int do_ioctl(struct inode * inode, struct file *filp,
ian@0 246 u_int cmd, u_long arg)
ian@0 247 {
ian@0 248 struct apm_user * as;
ian@0 249 DECLARE_WAITQUEUE(wait, current);
ian@0 250
ian@0 251 as = filp->private_data;
ian@0 252 if (check_apm_user(as, "ioctl"))
ian@0 253 return -EIO;
ian@0 254 if (!as->suser)
ian@0 255 return -EPERM;
ian@0 256 switch (cmd) {
ian@0 257 case APM_IOC_SUSPEND:
ian@0 258 /* If a suspend message was sent to userland, we
ian@0 259 * consider this as a confirmation message
ian@0 260 */
ian@0 261 if (as->suspends_read > 0) {
ian@0 262 as->suspends_read--;
ian@0 263 as->suspends_pending--;
ian@0 264 suspends_pending--;
ian@0 265 } else {
ian@0 266 // Route to PMU suspend ?
ian@0 267 break;
ian@0 268 }
ian@0 269 as->suspend_waiting = 1;
ian@0 270 add_wait_queue(&apm_waitqueue, &wait);
ian@0 271 DBG("apm_emu: ioctl waking up sleep waiter !\n");
ian@0 272 wake_up(&apm_suspend_waitqueue);
ian@0 273 mb();
ian@0 274 while(as->suspend_waiting && !signal_pending(current)) {
ian@0 275 set_current_state(TASK_INTERRUPTIBLE);
ian@0 276 schedule();
ian@0 277 }
ian@0 278 set_current_state(TASK_RUNNING);
ian@0 279 remove_wait_queue(&apm_waitqueue, &wait);
ian@0 280 break;
ian@0 281 default:
ian@0 282 return -EINVAL;
ian@0 283 }
ian@0 284 return 0;
ian@0 285 }
ian@0 286
ian@0 287 static int do_release(struct inode * inode, struct file * filp)
ian@0 288 {
ian@0 289 struct apm_user * as;
ian@0 290
ian@0 291 as = filp->private_data;
ian@0 292 if (check_apm_user(as, "release"))
ian@0 293 return 0;
ian@0 294 filp->private_data = NULL;
ian@0 295 lock_kernel();
ian@0 296 if (as->suspends_pending > 0) {
ian@0 297 suspends_pending -= as->suspends_pending;
ian@0 298 if (suspends_pending <= 0)
ian@0 299 wake_up(&apm_suspend_waitqueue);
ian@0 300 }
ian@0 301 if (user_list == as)
ian@0 302 user_list = as->next;
ian@0 303 else {
ian@0 304 struct apm_user * as1;
ian@0 305
ian@0 306 for (as1 = user_list;
ian@0 307 (as1 != NULL) && (as1->next != as);
ian@0 308 as1 = as1->next)
ian@0 309 ;
ian@0 310 if (as1 == NULL)
ian@0 311 printk(KERN_ERR "apm: filp not in user list\n");
ian@0 312 else
ian@0 313 as1->next = as->next;
ian@0 314 }
ian@0 315 unlock_kernel();
ian@0 316 kfree(as);
ian@0 317 return 0;
ian@0 318 }
ian@0 319
ian@0 320 static int do_open(struct inode * inode, struct file * filp)
ian@0 321 {
ian@0 322 struct apm_user * as;
ian@0 323
ian@0 324 as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
ian@0 325 if (as == NULL) {
ian@0 326 printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
ian@0 327 sizeof(*as));
ian@0 328 return -ENOMEM;
ian@0 329 }
ian@0 330 as->magic = APM_BIOS_MAGIC;
ian@0 331 as->event_tail = as->event_head = 0;
ian@0 332 as->suspends_pending = 0;
ian@0 333 as->suspends_read = 0;
ian@0 334 /*
ian@0 335 * XXX - this is a tiny bit broken, when we consider BSD
ian@0 336 * process accounting. If the device is opened by root, we
ian@0 337 * instantly flag that we used superuser privs. Who knows,
ian@0 338 * we might close the device immediately without doing a
ian@0 339 * privileged operation -- cevans
ian@0 340 */
ian@0 341 as->suser = capable(CAP_SYS_ADMIN);
ian@0 342 as->next = user_list;
ian@0 343 user_list = as;
ian@0 344 filp->private_data = as;
ian@0 345
ian@0 346 DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser);
ian@0 347
ian@0 348 return 0;
ian@0 349 }
ian@0 350
ian@0 351 /* Wait for all clients to ack the suspend request. APM API
ian@0 352 * doesn't provide a way to NAK, but this could be added
ian@0 353 * here.
ian@0 354 */
ian@0 355 static int wait_all_suspend(void)
ian@0 356 {
ian@0 357 DECLARE_WAITQUEUE(wait, current);
ian@0 358
ian@0 359 add_wait_queue(&apm_suspend_waitqueue, &wait);
ian@0 360 DBG("apm_emu: wait_all_suspend(), suspends_pending: %d\n", suspends_pending);
ian@0 361 while(suspends_pending > 0) {
ian@0 362 set_current_state(TASK_UNINTERRUPTIBLE);
ian@0 363 schedule();
ian@0 364 }
ian@0 365 set_current_state(TASK_RUNNING);
ian@0 366 remove_wait_queue(&apm_suspend_waitqueue, &wait);
ian@0 367
ian@0 368 DBG("apm_emu: wait_all_suspend() - complete !\n");
ian@0 369
ian@0 370 return 1;
ian@0 371 }
ian@0 372
ian@0 373 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
ian@0 374 {
ian@0 375 switch(when) {
ian@0 376 case PBOOK_SLEEP_REQUEST:
ian@0 377 queue_event(APM_SYS_SUSPEND, NULL);
ian@0 378 if (!wait_all_suspend())
ian@0 379 return PBOOK_SLEEP_REFUSE;
ian@0 380 break;
ian@0 381 case PBOOK_SLEEP_REJECT:
ian@0 382 case PBOOK_WAKE:
ian@0 383 queue_event(APM_NORMAL_RESUME, NULL);
ian@0 384 break;
ian@0 385 }
ian@0 386 return PBOOK_SLEEP_OK;
ian@0 387 }
ian@0 388
ian@0 389 #define APM_CRITICAL 10
ian@0 390 #define APM_LOW 30
ian@0 391
ian@0 392 static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length)
ian@0 393 {
ian@0 394 /* Arguments, with symbols from linux/apm_bios.h. Information is
ian@0 395 from the Get Power Status (0x0a) call unless otherwise noted.
ian@0 396
ian@0 397 0) Linux driver version (this will change if format changes)
ian@0 398 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
ian@0 399 2) APM flags from APM Installation Check (0x00):
ian@0 400 bit 0: APM_16_BIT_SUPPORT
ian@0 401 bit 1: APM_32_BIT_SUPPORT
ian@0 402 bit 2: APM_IDLE_SLOWS_CLOCK
ian@0 403 bit 3: APM_BIOS_DISABLED
ian@0 404 bit 4: APM_BIOS_DISENGAGED
ian@0 405 3) AC line status
ian@0 406 0x00: Off-line
ian@0 407 0x01: On-line
ian@0 408 0x02: On backup power (BIOS >= 1.1 only)
ian@0 409 0xff: Unknown
ian@0 410 4) Battery status
ian@0 411 0x00: High
ian@0 412 0x01: Low
ian@0 413 0x02: Critical
ian@0 414 0x03: Charging
ian@0 415 0x04: Selected battery not present (BIOS >= 1.2 only)
ian@0 416 0xff: Unknown
ian@0 417 5) Battery flag
ian@0 418 bit 0: High
ian@0 419 bit 1: Low
ian@0 420 bit 2: Critical
ian@0 421 bit 3: Charging
ian@0 422 bit 7: No system battery
ian@0 423 0xff: Unknown
ian@0 424 6) Remaining battery life (percentage of charge):
ian@0 425 0-100: valid
ian@0 426 -1: Unknown
ian@0 427 7) Remaining battery life (time units):
ian@0 428 Number of remaining minutes or seconds
ian@0 429 -1: Unknown
ian@0 430 8) min = minutes; sec = seconds */
ian@0 431
ian@0 432 unsigned short ac_line_status;
ian@0 433 unsigned short battery_status = 0;
ian@0 434 unsigned short battery_flag = 0xff;
ian@0 435 int percentage = -1;
ian@0 436 int time_units = -1;
ian@0 437 int real_count = 0;
ian@0 438 int i;
ian@0 439 char * p = buf;
ian@0 440 char charging = 0;
ian@0 441 long charge = -1;
ian@0 442 long amperage = 0;
ian@0 443 unsigned long btype = 0;
ian@0 444
ian@0 445 ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0);
ian@0 446 for (i=0; i<pmu_battery_count; i++) {
ian@0 447 if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
ian@0 448 battery_status++;
ian@0 449 if (percentage < 0)
ian@0 450 percentage = 0;
ian@0 451 if (charge < 0)
ian@0 452 charge = 0;
ian@0 453 percentage += (pmu_batteries[i].charge * 100) /
ian@0 454 pmu_batteries[i].max_charge;
ian@0 455 charge += pmu_batteries[i].charge;
ian@0 456 amperage += pmu_batteries[i].amperage;
ian@0 457 if (btype == 0)
ian@0 458 btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK);
ian@0 459 real_count++;
ian@0 460 if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
ian@0 461 charging++;
ian@0 462 }
ian@0 463 }
ian@0 464 if (0 == battery_status)
ian@0 465 ac_line_status = 1;
ian@0 466 battery_status = 0xff;
ian@0 467 if (real_count) {
ian@0 468 if (amperage < 0) {
ian@0 469 if (btype == PMU_BATT_TYPE_SMART)
ian@0 470 time_units = (charge * 59) / (amperage * -1);
ian@0 471 else
ian@0 472 time_units = (charge * 16440) / (amperage * -60);
ian@0 473 }
ian@0 474 percentage /= real_count;
ian@0 475 if (charging > 0) {
ian@0 476 battery_status = 0x03;
ian@0 477 battery_flag = 0x08;
ian@0 478 } else if (percentage <= APM_CRITICAL) {
ian@0 479 battery_status = 0x02;
ian@0 480 battery_flag = 0x04;
ian@0 481 } else if (percentage <= APM_LOW) {
ian@0 482 battery_status = 0x01;
ian@0 483 battery_flag = 0x02;
ian@0 484 } else {
ian@0 485 battery_status = 0x00;
ian@0 486 battery_flag = 0x01;
ian@0 487 }
ian@0 488 }
ian@0 489 p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
ian@0 490 driver_version,
ian@0 491 (FAKE_APM_BIOS_VERSION >> 8) & 0xff,
ian@0 492 FAKE_APM_BIOS_VERSION & 0xff,
ian@0 493 0,
ian@0 494 ac_line_status,
ian@0 495 battery_status,
ian@0 496 battery_flag,
ian@0 497 percentage,
ian@0 498 time_units,
ian@0 499 "min");
ian@0 500
ian@0 501 return p - buf;
ian@0 502 }
ian@0 503
ian@0 504 static struct file_operations apm_bios_fops = {
ian@0 505 .owner = THIS_MODULE,
ian@0 506 .read = do_read,
ian@0 507 .poll = do_poll,
ian@0 508 .ioctl = do_ioctl,
ian@0 509 .open = do_open,
ian@0 510 .release = do_release,
ian@0 511 };
ian@0 512
ian@0 513 static struct miscdevice apm_device = {
ian@0 514 APM_MINOR_DEV,
ian@0 515 "apm_bios",
ian@0 516 &apm_bios_fops
ian@0 517 };
ian@0 518
ian@0 519 static int __init apm_emu_init(void)
ian@0 520 {
ian@0 521 struct proc_dir_entry *apm_proc;
ian@0 522
ian@0 523 if (sys_ctrler != SYS_CTRLER_PMU) {
ian@0 524 printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
ian@0 525 return -ENODEV;
ian@0 526 }
ian@0 527
ian@0 528 apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
ian@0 529 if (apm_proc)
ian@0 530 apm_proc->owner = THIS_MODULE;
ian@0 531
ian@0 532 misc_register(&apm_device);
ian@0 533
ian@0 534 pmu_register_sleep_notifier(&apm_sleep_notifier);
ian@0 535
ian@0 536 printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
ian@0 537
ian@0 538 return 0;
ian@0 539 }
ian@0 540
ian@0 541 static void __exit apm_emu_exit(void)
ian@0 542 {
ian@0 543 pmu_unregister_sleep_notifier(&apm_sleep_notifier);
ian@0 544 misc_deregister(&apm_device);
ian@0 545 remove_proc_entry("apm", NULL);
ian@0 546
ian@0 547 printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
ian@0 548 }
ian@0 549
ian@0 550 module_init(apm_emu_init);
ian@0 551 module_exit(apm_emu_exit);
ian@0 552
ian@0 553 MODULE_AUTHOR("Benjamin Herrenschmidt");
ian@0 554 MODULE_DESCRIPTION("APM emulation layer for PowerMac");
ian@0 555 MODULE_LICENSE("GPL");
ian@0 556