| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  *  ibm_acpi.c - IBM ThinkPad ACPI Extras | 
 | 3 |  * | 
 | 4 |  * | 
 | 5 |  *  Copyright (C) 2004 Borislav Deianov | 
 | 6 |  * | 
 | 7 |  *  This program is free software; you can redistribute it and/or modify | 
 | 8 |  *  it under the terms of the GNU General Public License as published by | 
 | 9 |  *  the Free Software Foundation; either version 2 of the License, or | 
 | 10 |  *  (at your option) any later version. | 
 | 11 |  * | 
 | 12 |  *  This program is distributed in the hope that it will be useful, | 
 | 13 |  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 14 |  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 15 |  *  GNU General Public License for more details. | 
 | 16 |  * | 
 | 17 |  *  You should have received a copy of the GNU General Public License | 
 | 18 |  *  along with this program; if not, write to the Free Software | 
 | 19 |  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
 | 20 |  * | 
 | 21 |  *  Changelog: | 
 | 22 |  * | 
 | 23 |  *  2004-08-09	0.1	initial release, support for X series | 
 | 24 |  *  2004-08-14	0.2	support for T series, X20 | 
 | 25 |  *			bluetooth enable/disable | 
 | 26 |  *			hotkey events disabled by default | 
 | 27 |  *			removed fan control, currently useless | 
 | 28 |  *  2004-08-17	0.3	support for R40 | 
 | 29 |  *			lcd off, brightness control | 
 | 30 |  *			thinklight on/off | 
 | 31 |  *  2004-09-16	0.4	support for module parameters | 
 | 32 |  *			hotkey mask can be prefixed by 0x | 
 | 33 |  *			video output switching | 
 | 34 |  *			video expansion control | 
 | 35 |  *			ultrabay eject support | 
 | 36 |  *			removed lcd brightness/on/off control, didn't work | 
 | 37 |  *  2004-10-18	0.5	thinklight support on A21e, G40, R32, T20, T21, X20 | 
 | 38 |  *			proc file format changed | 
 | 39 |  *			video_switch command | 
 | 40 |  *			experimental cmos control | 
 | 41 |  *			experimental led control | 
 | 42 |  *			experimental acpi sounds | 
 | 43 |  *  2004-10-19	0.6	use acpi_bus_register_driver() to claim HKEY device | 
 | 44 |  *  2004-10-23	0.7	fix module loading on A21e, A22p, T20, T21, X20 | 
 | 45 |  *			fix LED control on A21e | 
 | 46 |  *  2004-11-08	0.8	fix init error case, don't return from a macro | 
 | 47 |  *				thanks to Chris Wright <chrisw@osdl.org> | 
 | 48 |  */ | 
 | 49 |  | 
 | 50 | #define IBM_VERSION "0.8" | 
 | 51 |  | 
 | 52 | #include <linux/kernel.h> | 
 | 53 | #include <linux/module.h> | 
 | 54 | #include <linux/init.h> | 
 | 55 | #include <linux/types.h> | 
 | 56 | #include <linux/proc_fs.h> | 
 | 57 | #include <asm/uaccess.h> | 
 | 58 |  | 
 | 59 | #include <acpi/acpi_drivers.h> | 
 | 60 | #include <acpi/acnamesp.h> | 
 | 61 |  | 
 | 62 | #define IBM_NAME "ibm" | 
 | 63 | #define IBM_DESC "IBM ThinkPad ACPI Extras" | 
 | 64 | #define IBM_FILE "ibm_acpi" | 
 | 65 | #define IBM_URL "http://ibm-acpi.sf.net/" | 
 | 66 |  | 
 | 67 | #define IBM_DIR IBM_NAME | 
 | 68 |  | 
 | 69 | #define IBM_LOG IBM_FILE ": " | 
 | 70 | #define IBM_ERR	   KERN_ERR    IBM_LOG | 
 | 71 | #define IBM_NOTICE KERN_NOTICE IBM_LOG | 
 | 72 | #define IBM_INFO   KERN_INFO   IBM_LOG | 
 | 73 | #define IBM_DEBUG  KERN_DEBUG  IBM_LOG | 
 | 74 |  | 
 | 75 | #define IBM_MAX_ACPI_ARGS 3 | 
 | 76 |  | 
 | 77 | #define __unused __attribute__ ((unused)) | 
 | 78 |  | 
 | 79 | static int experimental; | 
 | 80 | module_param(experimental, int, 0); | 
 | 81 |  | 
 | 82 | static acpi_handle root_handle = NULL; | 
 | 83 |  | 
 | 84 | #define IBM_HANDLE(object, parent, paths...)			\ | 
 | 85 | 	static acpi_handle  object##_handle;			\ | 
 | 86 | 	static acpi_handle *object##_parent = &parent##_handle;	\ | 
 | 87 | 	static char        *object##_paths[] = { paths } | 
 | 88 |  | 
 | 89 | IBM_HANDLE(ec, root, | 
 | 90 | 	   "\\_SB.PCI0.ISA.EC",    /* A21e, A22p, T20, T21, X20 */ | 
 | 91 | 	   "\\_SB.PCI0.LPC.EC",    /* all others */ | 
 | 92 | ); | 
 | 93 |  | 
 | 94 | IBM_HANDLE(vid, root,  | 
 | 95 | 	   "\\_SB.PCI0.VID",       /* A21e, G40, X30, X40 */ | 
 | 96 | 	   "\\_SB.PCI0.AGP.VID",   /* all others */ | 
 | 97 | ); | 
 | 98 |  | 
 | 99 | IBM_HANDLE(cmos, root, | 
 | 100 | 	   "\\UCMS",               /* R50, R50p, R51, T4x, X31, X40 */ | 
 | 101 | 	   "\\CMOS",               /* A3x, G40, R32, T23, T30, X22, X24, X30 */ | 
 | 102 | 	   "\\CMS",                /* R40, R40e */ | 
 | 103 | );                                 /* A21e, A22p, T20, T21, X20 */ | 
 | 104 |  | 
 | 105 | IBM_HANDLE(dock, root, | 
 | 106 | 	   "\\_SB.GDCK",           /* X30, X31, X40 */ | 
 | 107 | 	   "\\_SB.PCI0.DOCK",      /* A22p, T20, T21, X20 */ | 
 | 108 | 	   "\\_SB.PCI0.PCI1.DOCK", /* all others */ | 
 | 109 | );                                 /* A21e, G40, R32, R40, R40e */ | 
 | 110 |  | 
 | 111 | IBM_HANDLE(bay, root, | 
 | 112 | 	   "\\_SB.PCI0.IDE0.SCND.MSTR");      /* all except A21e */ | 
 | 113 | IBM_HANDLE(bayej, root, | 
 | 114 | 	   "\\_SB.PCI0.IDE0.SCND.MSTR._EJ0"); /* all except A2x, A3x */ | 
 | 115 |  | 
 | 116 | IBM_HANDLE(lght, root, "\\LGHT");  /* A21e, A22p, T20, T21, X20 */ | 
 | 117 | IBM_HANDLE(hkey, ec,   "HKEY");    /* all */ | 
 | 118 | IBM_HANDLE(led,  ec,   "LED");     /* all except A21e, A22p, T20, T21, X20 */ | 
 | 119 | IBM_HANDLE(sysl, ec,   "SYSL");    /* A21e, A22p, T20, T21, X20 */ | 
 | 120 | IBM_HANDLE(bled, ec,   "BLED");    /* A22p, T20, T21, X20 */ | 
 | 121 | IBM_HANDLE(beep, ec,   "BEEP");    /* all models */ | 
 | 122 |  | 
 | 123 | struct ibm_struct { | 
 | 124 | 	char *name; | 
 | 125 |  | 
 | 126 | 	char *hid; | 
 | 127 | 	struct acpi_driver *driver; | 
 | 128 | 	 | 
 | 129 | 	int  (*init)   (struct ibm_struct *); | 
 | 130 | 	int  (*read)   (struct ibm_struct *, char *); | 
 | 131 | 	int  (*write)  (struct ibm_struct *, char *); | 
 | 132 | 	void (*exit)   (struct ibm_struct *); | 
 | 133 |  | 
 | 134 | 	void (*notify) (struct ibm_struct *, u32);	 | 
 | 135 | 	acpi_handle *handle; | 
 | 136 | 	int type; | 
 | 137 | 	struct acpi_device *device; | 
 | 138 |  | 
 | 139 | 	int driver_registered; | 
 | 140 | 	int proc_created; | 
 | 141 | 	int init_called; | 
 | 142 | 	int notify_installed; | 
 | 143 |  | 
 | 144 | 	int supported; | 
 | 145 | 	union { | 
 | 146 | 		struct { | 
 | 147 | 			int status; | 
 | 148 | 			int mask; | 
 | 149 | 		} hotkey; | 
 | 150 | 		struct { | 
 | 151 | 			int autoswitch; | 
 | 152 | 		} video; | 
 | 153 | 	} state; | 
 | 154 |  | 
 | 155 | 	int experimental; | 
 | 156 | }; | 
 | 157 |  | 
 | 158 | static struct proc_dir_entry *proc_dir = NULL; | 
 | 159 |  | 
 | 160 | #define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off") | 
 | 161 | #define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") | 
 | 162 | #define strlencmp(a,b) (strncmp((a), (b), strlen(b))) | 
 | 163 |  | 
 | 164 | static int acpi_evalf(acpi_handle handle, | 
 | 165 | 		      void *res, char *method, char *fmt, ...) | 
 | 166 | { | 
 | 167 | 	char *fmt0 = fmt; | 
 | 168 |         struct acpi_object_list	params; | 
 | 169 |         union acpi_object	in_objs[IBM_MAX_ACPI_ARGS]; | 
 | 170 |         struct acpi_buffer	result; | 
 | 171 |         union acpi_object	out_obj; | 
 | 172 |         acpi_status		status; | 
 | 173 | 	va_list			ap; | 
 | 174 | 	char			res_type; | 
 | 175 | 	int			success; | 
 | 176 | 	int			quiet; | 
 | 177 |  | 
 | 178 | 	if (!*fmt) { | 
 | 179 | 		printk(IBM_ERR "acpi_evalf() called with empty format\n"); | 
 | 180 | 		return 0; | 
 | 181 | 	} | 
 | 182 |  | 
 | 183 | 	if (*fmt == 'q') { | 
 | 184 | 		quiet = 1; | 
 | 185 | 		fmt++; | 
 | 186 | 	} else | 
 | 187 | 		quiet = 0; | 
 | 188 |  | 
 | 189 | 	res_type = *(fmt++); | 
 | 190 |  | 
 | 191 | 	params.count = 0; | 
 | 192 | 	params.pointer = &in_objs[0]; | 
 | 193 |  | 
 | 194 | 	va_start(ap, fmt); | 
 | 195 | 	while (*fmt) { | 
 | 196 | 		char c = *(fmt++); | 
 | 197 | 		switch (c) { | 
 | 198 | 		case 'd':	/* int */ | 
 | 199 | 			in_objs[params.count].integer.value = va_arg(ap, int); | 
 | 200 | 			in_objs[params.count++].type = ACPI_TYPE_INTEGER; | 
 | 201 | 			break; | 
 | 202 | 		/* add more types as needed */ | 
 | 203 | 		default: | 
 | 204 | 			printk(IBM_ERR "acpi_evalf() called " | 
 | 205 | 			       "with invalid format character '%c'\n", c); | 
 | 206 | 			return 0; | 
 | 207 | 		} | 
 | 208 | 	} | 
 | 209 | 	va_end(ap); | 
 | 210 |  | 
 | 211 | 	result.length = sizeof(out_obj); | 
 | 212 | 	result.pointer = &out_obj; | 
 | 213 |  | 
 | 214 | 	status = acpi_evaluate_object(handle, method, ¶ms, &result); | 
 | 215 |  | 
 | 216 | 	switch (res_type) { | 
 | 217 | 	case 'd':	/* int */ | 
 | 218 | 		if (res) | 
 | 219 | 			*(int *)res = out_obj.integer.value; | 
 | 220 | 		success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; | 
 | 221 | 		break; | 
 | 222 | 	case 'v':	/* void */ | 
 | 223 | 		success = status == AE_OK; | 
 | 224 | 		break; | 
 | 225 | 	/* add more types as needed */ | 
 | 226 | 	default: | 
 | 227 | 		printk(IBM_ERR "acpi_evalf() called " | 
 | 228 | 		       "with invalid format character '%c'\n", res_type); | 
 | 229 | 		return 0; | 
 | 230 | 	} | 
 | 231 |  | 
 | 232 | 	if (!success && !quiet) | 
 | 233 | 		printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n", | 
 | 234 | 		       method, fmt0, status); | 
 | 235 |  | 
 | 236 | 	return success; | 
 | 237 | } | 
 | 238 |  | 
 | 239 | static void __unused acpi_print_int(acpi_handle handle, char *method) | 
 | 240 | { | 
 | 241 | 	int i; | 
 | 242 |  | 
 | 243 | 	if (acpi_evalf(handle, &i, method, "d")) | 
 | 244 | 		printk(IBM_INFO "%s = 0x%x\n", method, i); | 
 | 245 | 	else | 
 | 246 | 		printk(IBM_ERR "error calling %s\n", method); | 
 | 247 | } | 
 | 248 |  | 
 | 249 | static char *next_cmd(char **cmds) | 
 | 250 | { | 
 | 251 | 	char *start = *cmds; | 
 | 252 | 	char *end; | 
 | 253 |  | 
 | 254 | 	while ((end = strchr(start, ',')) && end == start) | 
 | 255 | 		start = end + 1; | 
 | 256 |  | 
 | 257 | 	if (!end) | 
 | 258 | 		return NULL; | 
 | 259 |  | 
 | 260 | 	*end = 0; | 
 | 261 | 	*cmds = end + 1; | 
 | 262 | 	return start; | 
 | 263 | } | 
 | 264 |  | 
 | 265 | static int driver_init(struct ibm_struct *ibm) | 
 | 266 | { | 
 | 267 | 	printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); | 
 | 268 | 	printk(IBM_INFO "%s\n", IBM_URL); | 
 | 269 |  | 
 | 270 | 	return 0; | 
 | 271 | } | 
 | 272 |  | 
 | 273 | static int driver_read(struct ibm_struct *ibm, char *p) | 
 | 274 | { | 
 | 275 | 	int len = 0; | 
 | 276 |  | 
 | 277 | 	len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC); | 
 | 278 | 	len += sprintf(p + len, "version:\t%s\n", IBM_VERSION); | 
 | 279 |  | 
 | 280 | 	return len; | 
 | 281 | } | 
 | 282 |  | 
 | 283 | static int hotkey_get(struct ibm_struct *ibm, int *status, int *mask) | 
 | 284 | { | 
 | 285 | 	if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) | 
 | 286 | 		return -EIO; | 
 | 287 | 	if (ibm->supported) { | 
 | 288 | 		if (!acpi_evalf(hkey_handle, mask, "DHKN", "qd")) | 
 | 289 | 			return -EIO; | 
 | 290 | 	} else { | 
 | 291 | 		*mask = ibm->state.hotkey.mask; | 
 | 292 | 	} | 
 | 293 | 	return 0; | 
 | 294 | } | 
 | 295 |  | 
 | 296 | static int hotkey_set(struct ibm_struct *ibm, int status, int mask) | 
 | 297 | { | 
 | 298 | 	int i; | 
 | 299 |  | 
 | 300 | 	if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) | 
 | 301 | 		return -EIO; | 
 | 302 |  | 
 | 303 | 	if (!ibm->supported) | 
 | 304 | 		return 0; | 
 | 305 |  | 
 | 306 | 	for (i=0; i<32; i++) { | 
 | 307 | 		int bit = ((1 << i) & mask) != 0; | 
 | 308 | 		if (!acpi_evalf(hkey_handle, NULL, "MHKM", "vdd", i+1, bit)) | 
 | 309 | 			return -EIO; | 
 | 310 | 	} | 
 | 311 |  | 
 | 312 | 	return 0; | 
 | 313 | } | 
 | 314 |  | 
 | 315 | static int hotkey_init(struct ibm_struct *ibm) | 
 | 316 | { | 
 | 317 | 	int ret; | 
 | 318 |  | 
 | 319 | 	ibm->supported = 1; | 
 | 320 | 	ret = hotkey_get(ibm, | 
 | 321 | 			 &ibm->state.hotkey.status, | 
 | 322 | 			 &ibm->state.hotkey.mask); | 
 | 323 | 	if (ret < 0) { | 
 | 324 | 		/* mask not supported on A21e, A22p, T20, T21, X20, X22, X24 */ | 
 | 325 | 		ibm->supported = 0; | 
 | 326 | 		ret = hotkey_get(ibm, | 
 | 327 | 				 &ibm->state.hotkey.status, | 
 | 328 | 				 &ibm->state.hotkey.mask); | 
 | 329 | 	} | 
 | 330 |  | 
 | 331 | 	return ret; | 
 | 332 | }	 | 
 | 333 |  | 
 | 334 | static int hotkey_read(struct ibm_struct *ibm, char *p) | 
 | 335 | { | 
 | 336 | 	int status, mask; | 
 | 337 | 	int len = 0; | 
 | 338 |  | 
 | 339 | 	if (hotkey_get(ibm, &status, &mask) < 0) | 
 | 340 | 		return -EIO; | 
 | 341 |  | 
 | 342 | 	len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); | 
 | 343 | 	if (ibm->supported) { | 
 | 344 | 		len += sprintf(p + len, "mask:\t\t0x%04x\n", mask); | 
 | 345 | 		len += sprintf(p + len, | 
 | 346 | 			       "commands:\tenable, disable, reset, <mask>\n"); | 
 | 347 | 	} else { | 
 | 348 | 		len += sprintf(p + len, "mask:\t\tnot supported\n"); | 
 | 349 | 		len += sprintf(p + len, "commands:\tenable, disable, reset\n"); | 
 | 350 | 	} | 
 | 351 |  | 
 | 352 | 	return len; | 
 | 353 | } | 
 | 354 |  | 
 | 355 | static int hotkey_write(struct ibm_struct *ibm, char *buf) | 
 | 356 | { | 
 | 357 | 	int status, mask; | 
 | 358 | 	char *cmd; | 
 | 359 | 	int do_cmd = 0; | 
 | 360 |  | 
 | 361 | 	if (hotkey_get(ibm, &status, &mask) < 0) | 
 | 362 | 		return -ENODEV; | 
 | 363 |  | 
 | 364 | 	while ((cmd = next_cmd(&buf))) { | 
 | 365 | 		if (strlencmp(cmd, "enable") == 0) { | 
 | 366 | 			status = 1; | 
 | 367 | 		} else if (strlencmp(cmd, "disable") == 0) { | 
 | 368 | 			status = 0; | 
 | 369 | 		} else if (strlencmp(cmd, "reset") == 0) { | 
 | 370 | 			status = ibm->state.hotkey.status; | 
 | 371 | 			mask   = ibm->state.hotkey.mask; | 
 | 372 | 		} else if (sscanf(cmd, "0x%x", &mask) == 1) { | 
 | 373 | 			/* mask set */ | 
 | 374 | 		} else if (sscanf(cmd, "%x", &mask) == 1) { | 
 | 375 | 			/* mask set */ | 
 | 376 | 		} else | 
 | 377 | 			return -EINVAL; | 
 | 378 | 		do_cmd = 1; | 
 | 379 | 	} | 
 | 380 |  | 
 | 381 | 	if (do_cmd && hotkey_set(ibm, status, mask) < 0) | 
 | 382 | 		return -EIO; | 
 | 383 |  | 
 | 384 | 	return 0; | 
 | 385 | }	 | 
 | 386 |  | 
 | 387 | static void hotkey_exit(struct ibm_struct *ibm) | 
 | 388 | { | 
 | 389 | 	hotkey_set(ibm, ibm->state.hotkey.status, ibm->state.hotkey.mask); | 
 | 390 | } | 
 | 391 |  | 
 | 392 | static void hotkey_notify(struct ibm_struct *ibm, u32 event) | 
 | 393 | { | 
 | 394 | 	int hkey; | 
 | 395 |  | 
 | 396 | 	if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) | 
 | 397 | 		acpi_bus_generate_event(ibm->device, event, hkey); | 
 | 398 | 	else { | 
 | 399 | 		printk(IBM_ERR "unknown hotkey event %d\n", event); | 
 | 400 | 		acpi_bus_generate_event(ibm->device, event, 0); | 
 | 401 | 	}	 | 
 | 402 | } | 
 | 403 |  | 
 | 404 | static int bluetooth_init(struct ibm_struct *ibm) | 
 | 405 | { | 
 | 406 | 	/* bluetooth not supported on A21e, G40, T20, T21, X20 */ | 
 | 407 | 	ibm->supported = acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); | 
 | 408 |  | 
 | 409 | 	return 0; | 
 | 410 | } | 
 | 411 |  | 
 | 412 | static int bluetooth_status(struct ibm_struct *ibm) | 
 | 413 | { | 
 | 414 | 	int status; | 
 | 415 |  | 
 | 416 | 	if (!ibm->supported || !acpi_evalf(hkey_handle, &status, "GBDC", "d")) | 
 | 417 | 		status = 0; | 
 | 418 |  | 
 | 419 | 	return status; | 
 | 420 | } | 
 | 421 |  | 
 | 422 | static int bluetooth_read(struct ibm_struct *ibm, char *p) | 
 | 423 | { | 
 | 424 | 	int len = 0; | 
 | 425 | 	int status = bluetooth_status(ibm); | 
 | 426 |  | 
 | 427 | 	if (!ibm->supported) | 
 | 428 | 		len += sprintf(p + len, "status:\t\tnot supported\n"); | 
 | 429 | 	else if (!(status & 1)) | 
 | 430 | 		len += sprintf(p + len, "status:\t\tnot installed\n"); | 
 | 431 | 	else { | 
 | 432 | 		len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); | 
 | 433 | 		len += sprintf(p + len, "commands:\tenable, disable\n"); | 
 | 434 | 	} | 
 | 435 |  | 
 | 436 | 	return len; | 
 | 437 | } | 
 | 438 |  | 
 | 439 | static int bluetooth_write(struct ibm_struct *ibm, char *buf) | 
 | 440 | { | 
 | 441 | 	int status = bluetooth_status(ibm); | 
 | 442 | 	char *cmd; | 
 | 443 | 	int do_cmd = 0; | 
 | 444 |  | 
 | 445 | 	if (!ibm->supported) | 
 | 446 | 		return -EINVAL; | 
 | 447 |  | 
 | 448 | 	while ((cmd = next_cmd(&buf))) { | 
 | 449 | 		if (strlencmp(cmd, "enable") == 0) { | 
 | 450 | 			status |= 2; | 
 | 451 | 		} else if (strlencmp(cmd, "disable") == 0) { | 
 | 452 | 			status &= ~2; | 
 | 453 | 		} else | 
 | 454 | 			return -EINVAL; | 
 | 455 | 		do_cmd = 1; | 
 | 456 | 	} | 
 | 457 |  | 
 | 458 | 	if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) | 
 | 459 | 	    return -EIO; | 
 | 460 |  | 
 | 461 | 	return 0; | 
 | 462 | } | 
 | 463 |  | 
 | 464 | static int video_init(struct ibm_struct *ibm) | 
 | 465 | { | 
 | 466 | 	if (!acpi_evalf(vid_handle, | 
 | 467 | 			&ibm->state.video.autoswitch, "^VDEE", "d")) | 
 | 468 | 		return -ENODEV; | 
 | 469 |  | 
 | 470 | 	return 0; | 
 | 471 | } | 
 | 472 |  | 
 | 473 | static int video_status(struct ibm_struct *ibm) | 
 | 474 | { | 
 | 475 | 	int status = 0; | 
 | 476 | 	int i; | 
 | 477 |  | 
 | 478 | 	acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1); | 
 | 479 | 	if (acpi_evalf(NULL, &i, "\\VCDC", "d")) | 
 | 480 | 		status |= 0x02 * i; | 
 | 481 |  | 
 | 482 | 	acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0); | 
 | 483 | 	if (acpi_evalf(NULL, &i, "\\VCDL", "d")) | 
 | 484 | 		status |= 0x01 * i; | 
 | 485 | 	if (acpi_evalf(NULL, &i, "\\VCDD", "d")) | 
 | 486 | 		status |= 0x08 * i; | 
 | 487 |  | 
 | 488 | 	if (acpi_evalf(vid_handle, &i, "^VDEE", "d")) | 
 | 489 | 		status |= 0x10 * (i & 1); | 
 | 490 |  | 
 | 491 | 	return status; | 
 | 492 | } | 
 | 493 |  | 
 | 494 | static int video_read(struct ibm_struct *ibm, char *p) | 
 | 495 | { | 
 | 496 | 	int status = video_status(ibm); | 
 | 497 | 	int len = 0; | 
 | 498 |  | 
 | 499 | 	len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0)); | 
 | 500 | 	len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1)); | 
 | 501 | 	len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); | 
 | 502 | 	len += sprintf(p + len, "auto:\t\t%s\n", enabled(status, 4)); | 
 | 503 | 	len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable, " | 
 | 504 | 		       "crt_enable, crt_disable\n"); | 
 | 505 | 	len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable, " | 
 | 506 | 		       "auto_enable, auto_disable\n"); | 
 | 507 | 	len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); | 
 | 508 |  | 
 | 509 | 	return len; | 
 | 510 | } | 
 | 511 |  | 
 | 512 | static int video_write(struct ibm_struct *ibm, char *buf) | 
 | 513 | { | 
 | 514 | 	char *cmd; | 
 | 515 | 	int enable, disable, status; | 
 | 516 |  | 
 | 517 | 	enable = disable = 0; | 
 | 518 |  | 
 | 519 | 	while ((cmd = next_cmd(&buf))) { | 
 | 520 | 		if (strlencmp(cmd, "lcd_enable") == 0) { | 
 | 521 | 			enable |= 0x01; | 
 | 522 | 		} else if (strlencmp(cmd, "lcd_disable") == 0) { | 
 | 523 | 			disable |= 0x01; | 
 | 524 | 		} else if (strlencmp(cmd, "crt_enable") == 0) { | 
 | 525 | 			enable |= 0x02; | 
 | 526 | 		} else if (strlencmp(cmd, "crt_disable") == 0) { | 
 | 527 | 			disable |= 0x02; | 
 | 528 | 		} else if (strlencmp(cmd, "dvi_enable") == 0) { | 
 | 529 | 			enable |= 0x08; | 
 | 530 | 		} else if (strlencmp(cmd, "dvi_disable") == 0) { | 
 | 531 | 			disable |= 0x08; | 
 | 532 | 		} else if (strlencmp(cmd, "auto_enable") == 0) { | 
 | 533 | 			if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) | 
 | 534 | 				return -EIO; | 
 | 535 | 		} else if (strlencmp(cmd, "auto_disable") == 0) { | 
 | 536 | 			if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0)) | 
 | 537 | 				return -EIO; | 
 | 538 | 		} else if (strlencmp(cmd, "video_switch") == 0) { | 
 | 539 | 			int autoswitch; | 
 | 540 | 			if (!acpi_evalf(vid_handle, &autoswitch, "^VDEE", "d")) | 
 | 541 | 				return -EIO; | 
 | 542 | 			if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) | 
 | 543 | 				return -EIO; | 
 | 544 | 			if (!acpi_evalf(vid_handle, NULL, "VSWT", "v")) | 
 | 545 | 				return -EIO; | 
 | 546 | 			if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", | 
 | 547 | 					autoswitch)) | 
 | 548 | 				return -EIO; | 
 | 549 | 		} else if (strlencmp(cmd, "expand_toggle") == 0) { | 
 | 550 | 			if (!acpi_evalf(NULL, NULL, "\\VEXP", "v")) | 
 | 551 | 				return -EIO; | 
 | 552 | 		} else | 
 | 553 | 			return -EINVAL; | 
 | 554 | 	} | 
 | 555 |  | 
 | 556 | 	if (enable || disable) { | 
 | 557 | 		status = (video_status(ibm) & 0x0f & ~disable) | enable; | 
 | 558 | 		if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80)) | 
 | 559 | 			return -EIO; | 
 | 560 | 		if (!acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1)) | 
 | 561 | 			return -EIO; | 
 | 562 | 	} | 
 | 563 |  | 
 | 564 | 	return 0; | 
 | 565 | } | 
 | 566 |  | 
 | 567 | static void video_exit(struct ibm_struct *ibm) | 
 | 568 | { | 
 | 569 | 	acpi_evalf(vid_handle, NULL, "_DOS", "vd", | 
 | 570 | 		   ibm->state.video.autoswitch); | 
 | 571 | } | 
 | 572 |  | 
 | 573 | static int light_init(struct ibm_struct *ibm) | 
 | 574 | { | 
 | 575 | 	/* kblt not supported on G40, R32, X20 */ | 
 | 576 | 	ibm->supported = acpi_evalf(ec_handle, NULL, "KBLT", "qv"); | 
 | 577 |  | 
 | 578 | 	return 0; | 
 | 579 | } | 
 | 580 |  | 
 | 581 | static int light_read(struct ibm_struct *ibm, char *p) | 
 | 582 | { | 
 | 583 | 	int len = 0; | 
 | 584 | 	int status = 0; | 
 | 585 |  | 
 | 586 | 	if (ibm->supported) { | 
 | 587 | 		if (!acpi_evalf(ec_handle, &status, "KBLT", "d")) | 
 | 588 | 			return -EIO; | 
 | 589 | 		len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); | 
 | 590 | 	} else | 
 | 591 | 		len += sprintf(p + len, "status:\t\tunknown\n"); | 
 | 592 |  | 
 | 593 | 	len += sprintf(p + len, "commands:\ton, off\n"); | 
 | 594 |  | 
 | 595 | 	return len; | 
 | 596 | } | 
 | 597 |  | 
 | 598 | static int light_write(struct ibm_struct *ibm, char *buf) | 
 | 599 | { | 
 | 600 | 	int cmos_cmd, lght_cmd; | 
 | 601 | 	char *cmd; | 
 | 602 | 	int success; | 
 | 603 | 	 | 
 | 604 | 	while ((cmd = next_cmd(&buf))) { | 
 | 605 | 		if (strlencmp(cmd, "on") == 0) { | 
 | 606 | 			cmos_cmd = 0x0c; | 
 | 607 | 			lght_cmd = 1; | 
 | 608 | 		} else if (strlencmp(cmd, "off") == 0) { | 
 | 609 | 			cmos_cmd = 0x0d; | 
 | 610 | 			lght_cmd = 0; | 
 | 611 | 		} else | 
 | 612 | 			return -EINVAL; | 
 | 613 | 		 | 
 | 614 | 		success = cmos_handle ? | 
 | 615 | 			acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) : | 
 | 616 | 			acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd); | 
 | 617 | 		if (!success) | 
 | 618 | 			return -EIO; | 
 | 619 | 	} | 
 | 620 |  | 
 | 621 | 	return 0; | 
 | 622 | } | 
 | 623 |  | 
 | 624 | static int _sta(acpi_handle handle) | 
 | 625 | { | 
 | 626 | 	int status; | 
 | 627 |  | 
 | 628 | 	if (!handle || !acpi_evalf(handle, &status, "_STA", "d")) | 
 | 629 | 		status = 0; | 
 | 630 |  | 
 | 631 | 	return status; | 
 | 632 | } | 
 | 633 |  | 
 | 634 | #define dock_docked() (_sta(dock_handle) & 1) | 
 | 635 |  | 
 | 636 | static int dock_read(struct ibm_struct *ibm, char *p) | 
 | 637 | { | 
 | 638 | 	int len = 0; | 
 | 639 | 	int docked = dock_docked(); | 
 | 640 |  | 
 | 641 | 	if (!dock_handle) | 
 | 642 | 		len += sprintf(p + len, "status:\t\tnot supported\n"); | 
 | 643 | 	else if (!docked) | 
 | 644 | 		len += sprintf(p + len, "status:\t\tundocked\n"); | 
 | 645 | 	else { | 
 | 646 | 		len += sprintf(p + len, "status:\t\tdocked\n"); | 
 | 647 | 		len += sprintf(p + len, "commands:\tdock, undock\n"); | 
 | 648 | 	} | 
 | 649 |  | 
 | 650 | 	return len; | 
 | 651 | } | 
 | 652 |  | 
 | 653 | static int dock_write(struct ibm_struct *ibm, char *buf) | 
 | 654 | { | 
 | 655 | 	char *cmd; | 
 | 656 |  | 
 | 657 | 	if (!dock_docked()) | 
 | 658 | 		return -EINVAL; | 
 | 659 |  | 
 | 660 | 	while ((cmd = next_cmd(&buf))) { | 
 | 661 | 		if (strlencmp(cmd, "undock") == 0) { | 
 | 662 | 			if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0)) | 
 | 663 | 				return -EIO; | 
 | 664 | 			if (!acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1)) | 
 | 665 | 				return -EIO; | 
 | 666 | 		} else if (strlencmp(cmd, "dock") == 0) { | 
 | 667 | 			if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1)) | 
 | 668 | 				return -EIO; | 
 | 669 | 		} else | 
 | 670 | 			return -EINVAL; | 
 | 671 | 	} | 
 | 672 |  | 
 | 673 | 	return 0; | 
 | 674 | }	 | 
 | 675 |  | 
 | 676 | static void dock_notify(struct ibm_struct *ibm, u32 event) | 
 | 677 | { | 
 | 678 | 	int docked = dock_docked(); | 
 | 679 |  | 
 | 680 | 	if (event == 3 && docked) | 
 | 681 | 		acpi_bus_generate_event(ibm->device, event, 1); /* button */ | 
 | 682 | 	else if (event == 3 && !docked) | 
 | 683 | 		acpi_bus_generate_event(ibm->device, event, 2); /* undock */ | 
 | 684 | 	else if (event == 0 && docked) | 
 | 685 | 		acpi_bus_generate_event(ibm->device, event, 3); /* dock */ | 
 | 686 | 	else { | 
 | 687 | 		printk(IBM_ERR "unknown dock event %d, status %d\n", | 
 | 688 | 		       event, _sta(dock_handle)); | 
 | 689 | 		acpi_bus_generate_event(ibm->device, event, 0); /* unknown */ | 
 | 690 | 	} | 
 | 691 | } | 
 | 692 |  | 
 | 693 | #define bay_occupied() (_sta(bay_handle) & 1) | 
 | 694 |  | 
 | 695 | static int bay_init(struct ibm_struct *ibm) | 
 | 696 | { | 
 | 697 | 	/* bay not supported on A21e, A22p, A31, A31p, G40, R32, R40e */ | 
 | 698 | 	ibm->supported = bay_handle && bayej_handle && | 
 | 699 | 		acpi_evalf(bay_handle, NULL, "_STA", "qv"); | 
 | 700 |  | 
 | 701 | 	return 0; | 
 | 702 | } | 
 | 703 |  | 
 | 704 | static int bay_read(struct ibm_struct *ibm, char *p) | 
 | 705 | { | 
 | 706 | 	int len = 0; | 
 | 707 | 	int occupied = bay_occupied(); | 
 | 708 | 	 | 
 | 709 | 	if (!ibm->supported) | 
 | 710 | 		len += sprintf(p + len, "status:\t\tnot supported\n"); | 
 | 711 | 	else if (!occupied) | 
 | 712 | 		len += sprintf(p + len, "status:\t\tunoccupied\n"); | 
 | 713 | 	else { | 
 | 714 | 		len += sprintf(p + len, "status:\t\toccupied\n"); | 
 | 715 | 		len += sprintf(p + len, "commands:\teject\n"); | 
 | 716 | 	} | 
 | 717 |  | 
 | 718 | 	return len; | 
 | 719 | } | 
 | 720 |  | 
 | 721 | static int bay_write(struct ibm_struct *ibm, char *buf) | 
 | 722 | { | 
 | 723 | 	char *cmd; | 
 | 724 |  | 
 | 725 | 	while ((cmd = next_cmd(&buf))) { | 
 | 726 | 		if (strlencmp(cmd, "eject") == 0) { | 
 | 727 | 			if (!ibm->supported || | 
 | 728 | 			    !acpi_evalf(bay_handle, NULL, "_EJ0", "vd", 1)) | 
 | 729 | 				return -EIO; | 
 | 730 | 		} else | 
 | 731 | 			return -EINVAL; | 
 | 732 | 	} | 
 | 733 |  | 
 | 734 | 	return 0; | 
 | 735 | }	 | 
 | 736 |  | 
 | 737 | static void bay_notify(struct ibm_struct *ibm, u32 event) | 
 | 738 | { | 
 | 739 | 	acpi_bus_generate_event(ibm->device, event, 0); | 
 | 740 | } | 
 | 741 |  | 
 | 742 | static int cmos_read(struct ibm_struct *ibm, char *p) | 
 | 743 | { | 
 | 744 | 	int len = 0; | 
 | 745 |  | 
 | 746 | 	/* cmos not supported on A21e, A22p, T20, T21, X20 */ | 
 | 747 | 	if (!cmos_handle) | 
 | 748 | 		len += sprintf(p + len, "status:\t\tnot supported\n"); | 
 | 749 | 	else { | 
 | 750 | 		len += sprintf(p + len, "status:\t\tsupported\n"); | 
 | 751 | 		len += sprintf(p + len, "commands:\t<int>\n"); | 
 | 752 | 	} | 
 | 753 |  | 
 | 754 | 	return len; | 
 | 755 | } | 
 | 756 |  | 
 | 757 | static int cmos_write(struct ibm_struct *ibm, char *buf) | 
 | 758 | { | 
 | 759 | 	char *cmd; | 
 | 760 | 	int cmos_cmd; | 
 | 761 |  | 
 | 762 | 	if (!cmos_handle) | 
 | 763 | 		return -EINVAL; | 
 | 764 |  | 
 | 765 | 	while ((cmd = next_cmd(&buf))) { | 
 | 766 | 		if (sscanf(cmd, "%u", &cmos_cmd) == 1) { | 
 | 767 | 			/* cmos_cmd set */ | 
 | 768 | 		} else | 
 | 769 | 			return -EINVAL; | 
 | 770 |  | 
 | 771 | 		if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd)) | 
 | 772 | 			return -EIO; | 
 | 773 | 	} | 
 | 774 |  | 
 | 775 | 	return 0; | 
 | 776 | }	 | 
 | 777 | 		 | 
 | 778 | static int led_read(struct ibm_struct *ibm, char *p) | 
 | 779 | { | 
 | 780 | 	int len = 0; | 
 | 781 |  | 
 | 782 | 	len += sprintf(p + len, "commands:\t" | 
 | 783 | 		       "<int> on, <int> off, <int> blink\n"); | 
 | 784 |  | 
 | 785 | 	return len; | 
 | 786 | } | 
 | 787 |  | 
 | 788 | static int led_write(struct ibm_struct *ibm, char *buf) | 
 | 789 | { | 
 | 790 | 	char *cmd; | 
 | 791 | 	unsigned int led; | 
 | 792 | 	int led_cmd, sysl_cmd, bled_a, bled_b; | 
 | 793 |  | 
 | 794 | 	while ((cmd = next_cmd(&buf))) { | 
 | 795 | 		if (sscanf(cmd, "%u", &led) != 1) | 
 | 796 | 			return -EINVAL; | 
 | 797 |  | 
 | 798 | 		if (strstr(cmd, "blink")) { | 
 | 799 | 			led_cmd = 0xc0; | 
 | 800 | 			sysl_cmd = 2; | 
 | 801 | 			bled_a = 2; | 
 | 802 | 			bled_b = 1; | 
 | 803 | 		} else if (strstr(cmd, "on")) { | 
 | 804 | 			led_cmd = 0x80; | 
 | 805 | 			sysl_cmd = 1; | 
 | 806 | 			bled_a = 2; | 
 | 807 | 			bled_b = 0; | 
 | 808 | 		} else if (strstr(cmd, "off")) { | 
 | 809 | 			led_cmd = sysl_cmd = bled_a = bled_b = 0; | 
 | 810 | 		} else | 
 | 811 | 			return -EINVAL; | 
 | 812 | 		 | 
 | 813 | 		if (led_handle) { | 
 | 814 | 			if (!acpi_evalf(led_handle, NULL, NULL, "vdd", | 
 | 815 | 					led, led_cmd)) | 
 | 816 | 				return -EIO; | 
 | 817 | 		} else if (led < 2) { | 
 | 818 | 			if (acpi_evalf(sysl_handle, NULL, NULL, "vdd", | 
 | 819 | 				       led, sysl_cmd)) | 
 | 820 | 				return -EIO; | 
 | 821 | 		} else if (led == 2 && bled_handle) { | 
 | 822 | 			if (acpi_evalf(bled_handle, NULL, NULL, "vdd", | 
 | 823 | 				       bled_a, bled_b)) | 
 | 824 | 				return -EIO; | 
 | 825 | 		} else | 
 | 826 | 			return -EINVAL; | 
 | 827 | 	} | 
 | 828 |  | 
 | 829 | 	return 0; | 
 | 830 | }	 | 
 | 831 | 		 | 
 | 832 | static int beep_read(struct ibm_struct *ibm, char *p) | 
 | 833 | { | 
 | 834 | 	int len = 0; | 
 | 835 |  | 
 | 836 | 	len += sprintf(p + len, "commands:\t<int>\n"); | 
 | 837 |  | 
 | 838 | 	return len; | 
 | 839 | } | 
 | 840 |  | 
 | 841 | static int beep_write(struct ibm_struct *ibm, char *buf) | 
 | 842 | { | 
 | 843 | 	char *cmd; | 
 | 844 | 	int beep_cmd; | 
 | 845 |  | 
 | 846 | 	while ((cmd = next_cmd(&buf))) { | 
 | 847 | 		if (sscanf(cmd, "%u", &beep_cmd) == 1) { | 
 | 848 | 			/* beep_cmd set */ | 
 | 849 | 		} else | 
 | 850 | 			return -EINVAL; | 
 | 851 |  | 
 | 852 | 		if (!acpi_evalf(beep_handle, NULL, NULL, "vd", beep_cmd)) | 
 | 853 | 			return -EIO; | 
 | 854 | 	} | 
 | 855 |  | 
 | 856 | 	return 0; | 
 | 857 | }	 | 
 | 858 | 		 | 
 | 859 | static struct ibm_struct ibms[] = { | 
 | 860 | 	{ | 
 | 861 | 		.name	= "driver", | 
 | 862 | 		.init	= driver_init, | 
 | 863 | 		.read	= driver_read, | 
 | 864 | 	}, | 
 | 865 | 	{ | 
 | 866 | 		.name	= "hotkey", | 
 | 867 | 		.hid	= "IBM0068", | 
 | 868 | 		.init	= hotkey_init, | 
 | 869 | 		.read	= hotkey_read, | 
 | 870 | 		.write	= hotkey_write, | 
 | 871 | 		.exit	= hotkey_exit, | 
 | 872 | 		.notify	= hotkey_notify, | 
 | 873 | 		.handle	= &hkey_handle, | 
 | 874 | 		.type	= ACPI_DEVICE_NOTIFY, | 
 | 875 | 	}, | 
 | 876 | 	{ | 
 | 877 | 		.name	= "bluetooth", | 
 | 878 | 		.init	= bluetooth_init, | 
 | 879 | 		.read	= bluetooth_read, | 
 | 880 | 		.write	= bluetooth_write, | 
 | 881 | 	}, | 
 | 882 | 	{ | 
 | 883 | 		.name	= "video", | 
 | 884 | 		.init	= video_init, | 
 | 885 | 		.read	= video_read, | 
 | 886 | 		.write	= video_write, | 
 | 887 | 		.exit	= video_exit, | 
 | 888 | 	}, | 
 | 889 | 	{ | 
 | 890 | 		.name	= "light", | 
 | 891 | 		.init	= light_init, | 
 | 892 | 		.read	= light_read, | 
 | 893 | 		.write	= light_write, | 
 | 894 | 	}, | 
 | 895 | 	{ | 
 | 896 | 		.name	= "dock", | 
 | 897 | 		.read	= dock_read, | 
 | 898 | 		.write	= dock_write, | 
 | 899 | 		.notify	= dock_notify, | 
 | 900 | 		.handle	= &dock_handle, | 
 | 901 | 		.type	= ACPI_SYSTEM_NOTIFY, | 
 | 902 | 	}, | 
 | 903 | 	{ | 
 | 904 | 		.name	= "bay", | 
 | 905 | 		.init	= bay_init, | 
 | 906 | 		.read	= bay_read, | 
 | 907 | 		.write	= bay_write, | 
 | 908 | 		.notify	= bay_notify, | 
 | 909 | 		.handle	= &bay_handle, | 
 | 910 | 		.type	= ACPI_SYSTEM_NOTIFY, | 
 | 911 | 	}, | 
 | 912 | 	{ | 
 | 913 | 		.name	= "cmos", | 
 | 914 | 		.read	= cmos_read, | 
 | 915 | 		.write	= cmos_write, | 
 | 916 | 		.experimental = 1, | 
 | 917 | 	}, | 
 | 918 | 	{ | 
 | 919 | 		.name	= "led", | 
 | 920 | 		.read	= led_read, | 
 | 921 | 		.write	= led_write, | 
 | 922 | 		.experimental = 1, | 
 | 923 | 	}, | 
 | 924 | 	{ | 
 | 925 | 		.name	= "beep", | 
 | 926 | 		.read	= beep_read, | 
 | 927 | 		.write	= beep_write, | 
 | 928 | 		.experimental = 1, | 
 | 929 | 	}, | 
 | 930 | }; | 
 | 931 | #define NUM_IBMS (sizeof(ibms)/sizeof(ibms[0])) | 
 | 932 |  | 
 | 933 | static int dispatch_read(char *page, char **start, off_t off, int count, | 
 | 934 | 			 int *eof, void *data) | 
 | 935 | { | 
 | 936 | 	struct ibm_struct *ibm = (struct ibm_struct *)data; | 
 | 937 | 	int len; | 
 | 938 | 	 | 
 | 939 | 	if (!ibm || !ibm->read) | 
 | 940 | 		return -EINVAL; | 
 | 941 |  | 
 | 942 | 	len = ibm->read(ibm, page); | 
 | 943 | 	if (len < 0) | 
 | 944 | 		return len; | 
 | 945 |  | 
 | 946 | 	if (len <= off + count) | 
 | 947 | 		*eof = 1; | 
 | 948 | 	*start = page + off; | 
 | 949 | 	len -= off; | 
 | 950 | 	if (len > count) | 
 | 951 | 		len = count; | 
 | 952 | 	if (len < 0) | 
 | 953 | 		len = 0; | 
 | 954 |  | 
 | 955 | 	return len; | 
 | 956 | } | 
 | 957 |  | 
 | 958 | static int dispatch_write(struct file *file, const char __user *userbuf, | 
 | 959 | 			  unsigned long count, void *data) | 
 | 960 | { | 
 | 961 | 	struct ibm_struct *ibm = (struct ibm_struct *)data; | 
 | 962 | 	char *kernbuf; | 
 | 963 | 	int ret; | 
 | 964 |  | 
 | 965 | 	if (!ibm || !ibm->write) | 
 | 966 | 		return -EINVAL; | 
 | 967 |  | 
 | 968 | 	kernbuf = kmalloc(count + 2, GFP_KERNEL); | 
 | 969 | 	if (!kernbuf) | 
 | 970 | 		return -ENOMEM; | 
 | 971 |  | 
 | 972 |         if (copy_from_user(kernbuf, userbuf, count)) { | 
 | 973 | 		kfree(kernbuf); | 
 | 974 |                 return -EFAULT; | 
 | 975 | 	} | 
 | 976 |  | 
 | 977 | 	kernbuf[count] = 0; | 
 | 978 | 	strcat(kernbuf, ","); | 
 | 979 | 	ret = ibm->write(ibm, kernbuf); | 
 | 980 | 	if (ret == 0) | 
 | 981 | 		ret = count; | 
 | 982 |  | 
 | 983 | 	kfree(kernbuf); | 
 | 984 |  | 
 | 985 |         return ret; | 
 | 986 | } | 
 | 987 |  | 
 | 988 | static void dispatch_notify(acpi_handle handle, u32 event, void *data) | 
 | 989 | { | 
 | 990 | 	struct ibm_struct *ibm = (struct ibm_struct *)data; | 
 | 991 |  | 
 | 992 | 	if (!ibm || !ibm->notify) | 
 | 993 | 		return; | 
 | 994 |  | 
 | 995 | 	ibm->notify(ibm, event); | 
 | 996 | } | 
 | 997 |  | 
 | 998 | static int setup_notify(struct ibm_struct *ibm) | 
 | 999 | { | 
 | 1000 | 	acpi_status status; | 
 | 1001 | 	int ret; | 
 | 1002 |  | 
 | 1003 | 	if (!*ibm->handle) | 
 | 1004 | 		return 0; | 
 | 1005 |  | 
 | 1006 | 	ret = acpi_bus_get_device(*ibm->handle, &ibm->device); | 
 | 1007 | 	if (ret < 0) { | 
 | 1008 | 		printk(IBM_ERR "%s device not present\n", ibm->name); | 
 | 1009 | 		return 0; | 
 | 1010 | 	} | 
 | 1011 |  | 
 | 1012 | 	acpi_driver_data(ibm->device) = ibm; | 
 | 1013 | 	sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name); | 
 | 1014 |  | 
 | 1015 | 	status = acpi_install_notify_handler(*ibm->handle, ibm->type, | 
 | 1016 | 					     dispatch_notify, ibm); | 
 | 1017 | 	if (ACPI_FAILURE(status)) { | 
 | 1018 | 		printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n", | 
 | 1019 | 		       ibm->name, status); | 
 | 1020 | 		return -ENODEV; | 
 | 1021 | 	} | 
 | 1022 |  | 
 | 1023 | 	ibm->notify_installed = 1; | 
 | 1024 |  | 
 | 1025 | 	return 0; | 
 | 1026 | } | 
 | 1027 |  | 
| David Shaohua Li | 4e10d12 | 2005-03-18 18:45:35 -0500 | [diff] [blame] | 1028 | static int ibmacpi_device_add(struct acpi_device *device) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1029 | { | 
 | 1030 | 	return 0; | 
 | 1031 | } | 
 | 1032 |  | 
 | 1033 | static int register_driver(struct ibm_struct *ibm) | 
 | 1034 | { | 
 | 1035 | 	int ret; | 
 | 1036 |  | 
 | 1037 | 	ibm->driver = kmalloc(sizeof(struct acpi_driver), GFP_KERNEL); | 
 | 1038 | 	if (!ibm->driver) { | 
 | 1039 | 		printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); | 
 | 1040 | 		return -1; | 
 | 1041 | 	} | 
 | 1042 |  | 
 | 1043 | 	memset(ibm->driver, 0, sizeof(struct acpi_driver)); | 
 | 1044 | 	sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name); | 
 | 1045 | 	ibm->driver->ids = ibm->hid; | 
| David Shaohua Li | 4e10d12 | 2005-03-18 18:45:35 -0500 | [diff] [blame] | 1046 | 	ibm->driver->ops.add = &ibmacpi_device_add; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1047 |  | 
 | 1048 | 	ret = acpi_bus_register_driver(ibm->driver); | 
 | 1049 | 	if (ret < 0) { | 
 | 1050 | 		printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", | 
 | 1051 | 		       ibm->hid, ret); | 
 | 1052 | 		kfree(ibm->driver); | 
 | 1053 | 	} | 
 | 1054 |  | 
 | 1055 | 	return ret; | 
 | 1056 | } | 
 | 1057 |  | 
 | 1058 | static int ibm_init(struct ibm_struct *ibm) | 
 | 1059 | { | 
 | 1060 | 	int ret; | 
 | 1061 | 	struct proc_dir_entry *entry; | 
 | 1062 |  | 
 | 1063 | 	if (ibm->experimental && !experimental) | 
 | 1064 | 		return 0; | 
 | 1065 |  | 
 | 1066 | 	if (ibm->hid) { | 
 | 1067 | 		ret = register_driver(ibm); | 
 | 1068 | 		if (ret < 0) | 
 | 1069 | 			return ret; | 
 | 1070 | 		ibm->driver_registered = 1; | 
 | 1071 | 	} | 
 | 1072 |  | 
 | 1073 | 	if (ibm->init) { | 
 | 1074 | 		ret = ibm->init(ibm); | 
 | 1075 | 		if (ret != 0) | 
 | 1076 | 			return ret; | 
 | 1077 | 		ibm->init_called = 1; | 
 | 1078 | 	} | 
 | 1079 |  | 
 | 1080 | 	entry = create_proc_entry(ibm->name, S_IFREG | S_IRUGO | S_IWUSR, | 
 | 1081 | 				  proc_dir); | 
 | 1082 | 	if (!entry) { | 
 | 1083 | 		printk(IBM_ERR "unable to create proc entry %s\n", ibm->name); | 
 | 1084 | 		return -ENODEV; | 
 | 1085 | 	} | 
 | 1086 | 	entry->owner = THIS_MODULE; | 
 | 1087 | 	ibm->proc_created = 1; | 
 | 1088 | 	 | 
 | 1089 | 	entry->data = ibm; | 
 | 1090 | 	if (ibm->read) | 
 | 1091 | 		entry->read_proc = &dispatch_read; | 
 | 1092 | 	if (ibm->write) | 
 | 1093 | 		entry->write_proc = &dispatch_write; | 
 | 1094 |  | 
 | 1095 | 	if (ibm->notify) { | 
 | 1096 | 		ret = setup_notify(ibm); | 
 | 1097 | 		if (ret < 0) | 
 | 1098 | 			return ret; | 
 | 1099 | 	} | 
 | 1100 |  | 
 | 1101 | 	return 0; | 
 | 1102 | } | 
 | 1103 |  | 
 | 1104 | static void ibm_exit(struct ibm_struct *ibm) | 
 | 1105 | { | 
 | 1106 | 	if (ibm->notify_installed) | 
 | 1107 | 		acpi_remove_notify_handler(*ibm->handle, ibm->type, | 
 | 1108 | 					   dispatch_notify); | 
 | 1109 |  | 
 | 1110 | 	if (ibm->proc_created) | 
 | 1111 | 		remove_proc_entry(ibm->name, proc_dir); | 
 | 1112 |  | 
 | 1113 | 	if (ibm->init_called && ibm->exit) | 
 | 1114 | 		ibm->exit(ibm); | 
 | 1115 |  | 
 | 1116 | 	if (ibm->driver_registered) { | 
 | 1117 | 		acpi_bus_unregister_driver(ibm->driver); | 
 | 1118 | 		kfree(ibm->driver); | 
 | 1119 | 	} | 
 | 1120 | } | 
 | 1121 |  | 
 | 1122 | static int ibm_handle_init(char *name, | 
 | 1123 | 			   acpi_handle *handle, acpi_handle parent, | 
 | 1124 | 			   char **paths, int num_paths, int required) | 
 | 1125 | { | 
 | 1126 | 	int i; | 
 | 1127 | 	acpi_status status; | 
 | 1128 |  | 
 | 1129 | 	for (i=0; i<num_paths; i++) { | 
 | 1130 | 		status = acpi_get_handle(parent, paths[i], handle); | 
 | 1131 | 		if (ACPI_SUCCESS(status)) | 
 | 1132 | 			return 0; | 
 | 1133 | 	} | 
 | 1134 | 	 | 
 | 1135 | 	*handle = NULL; | 
 | 1136 |  | 
 | 1137 | 	if (required) { | 
 | 1138 | 		printk(IBM_ERR "%s object not found\n", name); | 
 | 1139 | 		return -1; | 
 | 1140 | 	} | 
 | 1141 |  | 
 | 1142 | 	return 0; | 
 | 1143 | } | 
 | 1144 |  | 
 | 1145 | #define IBM_HANDLE_INIT(object, required)				\ | 
 | 1146 | 	ibm_handle_init(#object, &object##_handle, *object##_parent,	\ | 
 | 1147 | 		object##_paths, sizeof(object##_paths)/sizeof(char*), required) | 
 | 1148 |  | 
 | 1149 |  | 
 | 1150 | static int set_ibm_param(const char *val, struct kernel_param *kp) | 
 | 1151 | { | 
 | 1152 | 	unsigned int i; | 
 | 1153 | 	char arg_with_comma[32]; | 
 | 1154 |  | 
 | 1155 | 	if (strlen(val) > 30) | 
 | 1156 | 		return -ENOSPC; | 
 | 1157 |  | 
 | 1158 | 	strcpy(arg_with_comma, val); | 
 | 1159 | 	strcat(arg_with_comma, ","); | 
 | 1160 |  | 
 | 1161 | 	for (i=0; i<NUM_IBMS; i++) | 
 | 1162 | 		if (strcmp(ibms[i].name, kp->name) == 0) | 
 | 1163 | 			return ibms[i].write(&ibms[i], arg_with_comma); | 
 | 1164 | 	BUG(); | 
 | 1165 | 	return -EINVAL; | 
 | 1166 | } | 
 | 1167 |  | 
 | 1168 | #define IBM_PARAM(feature) \ | 
 | 1169 | 	module_param_call(feature, set_ibm_param, NULL, NULL, 0) | 
 | 1170 |  | 
 | 1171 | static void acpi_ibm_exit(void) | 
 | 1172 | { | 
 | 1173 | 	int i; | 
 | 1174 |  | 
 | 1175 | 	for (i=NUM_IBMS-1; i>=0; i--) | 
 | 1176 | 		ibm_exit(&ibms[i]); | 
 | 1177 |  | 
 | 1178 | 	remove_proc_entry(IBM_DIR, acpi_root_dir); | 
 | 1179 | } | 
 | 1180 |  | 
 | 1181 | static int __init acpi_ibm_init(void) | 
 | 1182 | { | 
 | 1183 | 	int ret, i; | 
 | 1184 |  | 
 | 1185 | 	if (acpi_disabled) | 
 | 1186 | 		return -ENODEV; | 
 | 1187 |  | 
| Luming Yu | fb9802f | 2005-03-18 18:03:45 -0500 | [diff] [blame] | 1188 | 	if (!acpi_specific_hotkey_enabled){ | 
 | 1189 | 		printk(IBM_ERR "Using generic hotkey driver\n"); | 
 | 1190 | 		return -ENODEV;	 | 
 | 1191 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1192 | 	/* these handles are required */ | 
 | 1193 | 	if (IBM_HANDLE_INIT(ec,	  1) < 0 || | 
 | 1194 | 	    IBM_HANDLE_INIT(hkey, 1) < 0 || | 
 | 1195 | 	    IBM_HANDLE_INIT(vid,  1) < 0 || | 
 | 1196 | 	    IBM_HANDLE_INIT(beep, 1) < 0) | 
 | 1197 | 		return -ENODEV; | 
 | 1198 |  | 
 | 1199 | 	/* these handles have alternatives */ | 
 | 1200 | 	IBM_HANDLE_INIT(lght, 0); | 
 | 1201 | 	if (IBM_HANDLE_INIT(cmos, !lght_handle) < 0) | 
 | 1202 | 		return -ENODEV; | 
 | 1203 | 	IBM_HANDLE_INIT(sysl, 0); | 
 | 1204 | 	if (IBM_HANDLE_INIT(led, !sysl_handle) < 0) | 
 | 1205 | 		return -ENODEV; | 
 | 1206 |  | 
 | 1207 | 	/* these handles are not required */ | 
 | 1208 | 	IBM_HANDLE_INIT(dock,  0); | 
 | 1209 | 	IBM_HANDLE_INIT(bay,   0); | 
 | 1210 | 	IBM_HANDLE_INIT(bayej, 0); | 
 | 1211 | 	IBM_HANDLE_INIT(bled,  0); | 
 | 1212 |  | 
 | 1213 | 	proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); | 
 | 1214 | 	if (!proc_dir) { | 
 | 1215 | 		printk(IBM_ERR "unable to create proc dir %s", IBM_DIR); | 
 | 1216 | 		return -ENODEV; | 
 | 1217 | 	} | 
 | 1218 | 	proc_dir->owner = THIS_MODULE; | 
 | 1219 | 	 | 
 | 1220 | 	for (i=0; i<NUM_IBMS; i++) { | 
 | 1221 | 		ret = ibm_init(&ibms[i]); | 
 | 1222 | 		if (ret < 0) { | 
 | 1223 | 			acpi_ibm_exit(); | 
 | 1224 | 			return ret; | 
 | 1225 | 		} | 
 | 1226 | 	} | 
 | 1227 |  | 
 | 1228 | 	return 0; | 
 | 1229 | } | 
 | 1230 |  | 
 | 1231 | module_init(acpi_ibm_init); | 
 | 1232 | module_exit(acpi_ibm_exit); | 
 | 1233 |  | 
 | 1234 | MODULE_AUTHOR("Borislav Deianov"); | 
 | 1235 | MODULE_DESCRIPTION(IBM_DESC); | 
 | 1236 | MODULE_LICENSE("GPL"); | 
 | 1237 |  | 
 | 1238 | IBM_PARAM(hotkey); | 
 | 1239 | IBM_PARAM(bluetooth); | 
 | 1240 | IBM_PARAM(video); | 
 | 1241 | IBM_PARAM(light); | 
 | 1242 | IBM_PARAM(dock); | 
 | 1243 | IBM_PARAM(bay); | 
 | 1244 | IBM_PARAM(cmos); | 
 | 1245 | IBM_PARAM(led); | 
 | 1246 | IBM_PARAM(beep); |