chromeos_laptop.c 15.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 *  chromeos_laptop.c - Driver to instantiate Chromebook i2c/smbus devices.
 *
 *  Author : Benson Leung <bleung@chromium.org>
 *
 *  Copyright (C) 2012 Google, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <linux/dmi.h>
#include <linux/i2c.h>
26
#include <linux/platform_data/atmel_mxt_ts.h>
27 28
#include <linux/input.h>
#include <linux/interrupt.h>
29
#include <linux/module.h>
30
#include <linux/platform_device.h>
31

32 33
#define ATMEL_TP_I2C_ADDR	0x4b
#define ATMEL_TP_I2C_BL_ADDR	0x25
34 35
#define ATMEL_TS_I2C_ADDR	0x4a
#define ATMEL_TS_I2C_BL_ADDR	0x26
36
#define CYAPA_TP_I2C_ADDR	0x67
37
#define ELAN_TP_I2C_ADDR	0x15
38
#define ISL_ALS_I2C_ADDR	0x44
39
#define TAOS_ALS_I2C_ADDR	0x29
40

41 42
#define MAX_I2C_DEVICE_DEFERRALS	5

43 44
static struct i2c_client *als;
static struct i2c_client *tp;
45
static struct i2c_client *ts;
46

47
static const char *i2c_adapter_names[] = {
48
	"SMBus I801 adapter",
49 50
	"i915 gmbus vga",
	"i915 gmbus panel",
51 52
	"Synopsys DesignWare I2C adapter",
	"Synopsys DesignWare I2C adapter",
53 54 55 56 57
};

/* Keep this enum consistent with i2c_adapter_names */
enum i2c_adapter_type {
	I2C_ADAPTER_SMBUS = 0,
58 59
	I2C_ADAPTER_VGADDC,
	I2C_ADAPTER_PANEL,
60 61
	I2C_ADAPTER_DESIGNWARE_0,
	I2C_ADAPTER_DESIGNWARE_1,
62 63
};

64 65 66 67
enum i2c_peripheral_state {
	UNPROBED = 0,
	PROBED,
	TIMEDOUT,
68 69
};

70
struct i2c_peripheral {
71
	int (*add)(enum i2c_adapter_type type);
72
	enum i2c_adapter_type type;
73 74
	enum i2c_peripheral_state state;
	int tries;
75 76
};

77
#define MAX_I2C_PERIPHERALS 4
78 79 80 81 82

struct chromeos_laptop {
	struct i2c_peripheral i2c_peripherals[MAX_I2C_PERIPHERALS];
};

83 84 85
static struct chromeos_laptop *cros_laptop;

static struct i2c_board_info cyapa_device = {
86 87 88 89
	I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR),
	.flags		= I2C_CLIENT_WAKE,
};

90 91 92 93 94
static struct i2c_board_info elantech_device = {
	I2C_BOARD_INFO("elan_i2c", ELAN_TP_I2C_ADDR),
	.flags		= I2C_CLIENT_WAKE,
};

95
static struct i2c_board_info isl_als_device = {
96 97 98
	I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR),
};

99
static struct i2c_board_info tsl2583_als_device = {
100 101 102
	I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR),
};

103
static struct i2c_board_info tsl2563_als_device = {
104 105 106
	I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR),
};

107 108 109 110 111 112 113 114 115
static int mxt_t19_keys[] = {
	KEY_RESERVED,
	KEY_RESERVED,
	KEY_RESERVED,
	KEY_RESERVED,
	KEY_RESERVED,
	BTN_LEFT
};

116 117
static struct mxt_platform_data atmel_224s_tp_platform_data = {
	.irqflags		= IRQF_TRIGGER_FALLING,
118 119
	.t19_num_keys		= ARRAY_SIZE(mxt_t19_keys),
	.t19_keymap		= mxt_t19_keys,
120
	.suspend_mode		= MXT_SUSPEND_T9_CTRL,
121 122
};

123
static struct i2c_board_info atmel_224s_tp_device = {
124
	I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR),
125
	.platform_data = &atmel_224s_tp_platform_data,
126 127 128
	.flags		= I2C_CLIENT_WAKE,
};

129 130
static struct mxt_platform_data atmel_1664s_platform_data = {
	.irqflags		= IRQF_TRIGGER_FALLING,
131
	.suspend_mode		= MXT_SUSPEND_T9_CTRL,
132 133
};

134
static struct i2c_board_info atmel_1664s_device = {
135
	I2C_BOARD_INFO("atmel_mxt_ts", ATMEL_TS_I2C_ADDR),
136
	.platform_data = &atmel_1664s_platform_data,
137 138 139
	.flags		= I2C_CLIENT_WAKE,
};

140
static struct i2c_client *__add_probed_i2c_device(
141 142 143
		const char *name,
		int bus,
		struct i2c_board_info *info,
144
		const unsigned short *alt_addr_list)
145 146 147 148
{
	const struct dmi_device *dmi_dev;
	const struct dmi_dev_onboard *dev_data;
	struct i2c_adapter *adapter;
149 150
	struct i2c_client *client = NULL;
	const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END };
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

	if (bus < 0)
		return NULL;
	/*
	 * If a name is specified, look for irq platform information stashed
	 * in DMI_DEV_TYPE_DEV_ONBOARD by the Chrome OS custom system firmware.
	 */
	if (name) {
		dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, name, NULL);
		if (!dmi_dev) {
			pr_err("%s failed to dmi find device %s.\n",
			       __func__,
			       name);
			return NULL;
		}
		dev_data = (struct dmi_dev_onboard *)dmi_dev->device_data;
		if (!dev_data) {
			pr_err("%s failed to get data from dmi for %s.\n",
			       __func__, name);
			return NULL;
		}
		info->irq = dev_data->instance;
	}

	adapter = i2c_get_adapter(bus);
	if (!adapter) {
		pr_err("%s failed to get i2c adapter %d.\n", __func__, bus);
		return NULL;
	}

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
	/*
	 * Add the i2c device. If we can't detect it at the primary
	 * address we scan secondary addresses. In any case the client
	 * structure gets assigned primary address.
	 */
	client = i2c_new_probed_device(adapter, info, addr_list, NULL);
	if (!client && alt_addr_list) {
		struct i2c_board_info dummy_info = {
			I2C_BOARD_INFO("dummy", info->addr),
		};
		struct i2c_client *dummy;

		dummy = i2c_new_probed_device(adapter, &dummy_info,
					      alt_addr_list, NULL);
		if (dummy) {
			pr_debug("%s %d-%02x is probed at %02x\n",
				  __func__, bus, info->addr, dummy->addr);
			i2c_unregister_device(dummy);
			client = i2c_new_device(adapter, info);
		}
	}

203
	if (!client)
204 205
		pr_notice("%s failed to register device %d-%02x\n",
			  __func__, bus, info->addr);
206 207 208 209 210 211 212 213
	else
		pr_debug("%s added i2c device %d-%02x\n",
			 __func__, bus, info->addr);

	i2c_put_adapter(adapter);
	return client;
}

214 215 216 217 218 219
struct i2c_lookup {
	const char *name;
	int instance;
	int n;
};

220
static int __find_i2c_adap(struct device *dev, void *data)
221
{
222
	struct i2c_lookup *lookup = data;
223 224
	static const char *prefix = "i2c-";
	struct i2c_adapter *adapter;
225

226 227 228
	if (strncmp(dev_name(dev), prefix, strlen(prefix)) != 0)
		return 0;
	adapter = to_i2c_adapter(dev);
229 230 231 232
	if (strncmp(adapter->name, lookup->name, strlen(lookup->name)) == 0 &&
	    lookup->n++ == lookup->instance)
		return 1;
	return 0;
233 234
}

235
static int find_i2c_adapter_num(enum i2c_adapter_type type)
236 237 238
{
	struct device *dev = NULL;
	struct i2c_adapter *adapter;
239 240 241 242 243 244
	struct i2c_lookup lookup;

	memset(&lookup, 0, sizeof(lookup));
	lookup.name = i2c_adapter_names[type];
	lookup.instance = (type == I2C_ADAPTER_DESIGNWARE_1) ? 1 : 0;

245
	/* find the adapter by name */
246
	dev = bus_find_device(&i2c_bus_type, NULL, &lookup, __find_i2c_adap);
247
	if (!dev) {
248 249
		/* Adapters may appear later. Deferred probing will retry */
		pr_notice("%s: i2c adapter %s not found on system.\n", __func__,
250
			  lookup.name);
251 252 253 254 255 256
		return -ENODEV;
	}
	adapter = to_i2c_adapter(dev);
	return adapter->nr;
}

257 258 259 260 261 262 263 264
/*
 * Takes a list of addresses in addrs as such :
 * { addr1, ... , addrn, I2C_CLIENT_END };
 * add_probed_i2c_device will use i2c_new_probed_device
 * and probe for devices at all of the addresses listed.
 * Returns NULL if no devices found.
 * See Documentation/i2c/instantiating-devices for more information.
 */
265
static struct i2c_client *add_probed_i2c_device(
266 267 268 269 270 271 272 273 274 275 276
		const char *name,
		enum i2c_adapter_type type,
		struct i2c_board_info *info,
		const unsigned short *addrs)
{
	return __add_probed_i2c_device(name,
				       find_i2c_adapter_num(type),
				       info,
				       addrs);
}

277 278 279 280 281
/*
 * Probes for a device at a single address, the one provided by
 * info->addr.
 * Returns NULL if no device found.
 */
282
static struct i2c_client *add_i2c_device(const char *name,
283 284
						enum i2c_adapter_type type,
						struct i2c_board_info *info)
285 286
{
	return __add_probed_i2c_device(name,
287
				       find_i2c_adapter_num(type),
288
				       info,
289
				       NULL);
290 291
}

292
static int setup_cyapa_tp(enum i2c_adapter_type type)
293
{
294 295 296
	if (tp)
		return 0;

297 298
	/* add cyapa touchpad */
	tp = add_i2c_device("trackpad", type, &cyapa_device);
299
	return (!tp) ? -EAGAIN : 0;
300 301
}

302
static int setup_atmel_224s_tp(enum i2c_adapter_type type)
303 304 305
{
	const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR,
					     I2C_CLIENT_END };
306 307
	if (tp)
		return 0;
308

309 310
	/* add atmel mxt touchpad */
	tp = add_probed_i2c_device("trackpad", type,
311
				   &atmel_224s_tp_device, addr_list);
312
	return (!tp) ? -EAGAIN : 0;
313 314
}

315 316 317 318 319 320 321 322 323 324
static int setup_elantech_tp(enum i2c_adapter_type type)
{
	if (tp)
		return 0;

	/* add elantech touchpad */
	tp = add_i2c_device("trackpad", type, &elantech_device);
	return (!tp) ? -EAGAIN : 0;
}

325
static int setup_atmel_1664s_ts(enum i2c_adapter_type type)
326 327 328
{
	const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR,
					     I2C_CLIENT_END };
329 330
	if (ts)
		return 0;
331

332 333
	/* add atmel mxt touch device */
	ts = add_probed_i2c_device("touchscreen", type,
334
				   &atmel_1664s_device, addr_list);
335
	return (!ts) ? -EAGAIN : 0;
336 337
}

338
static int setup_isl29018_als(enum i2c_adapter_type type)
339
{
340 341 342
	if (als)
		return 0;

343
	/* add isl29018 light sensor */
344
	als = add_i2c_device("lightsensor", type, &isl_als_device);
345
	return (!als) ? -EAGAIN : 0;
346 347
}

348
static int setup_tsl2583_als(enum i2c_adapter_type type)
349
{
350 351 352
	if (als)
		return 0;

353 354
	/* add tsl2583 light sensor */
	als = add_i2c_device(NULL, type, &tsl2583_als_device);
355
	return (!als) ? -EAGAIN : 0;
356 357
}

358
static int setup_tsl2563_als(enum i2c_adapter_type type)
359
{
360 361 362
	if (als)
		return 0;

363 364
	/* add tsl2563 light sensor */
	als = add_i2c_device(NULL, type, &tsl2563_als_device);
365
	return (!als) ? -EAGAIN : 0;
366 367
}

368
static int __init chromeos_laptop_dmi_matched(const struct dmi_system_id *id)
369
{
370 371
	cros_laptop = (void *)id->driver_data;
	pr_debug("DMI Matched %s.\n", id->ident);
372

373 374 375 376 377 378 379 380
	/* Indicate to dmi_scan that processing is done. */
	return 1;
}

static int chromeos_laptop_probe(struct platform_device *pdev)
{
	int i;
	int ret = 0;
381 382 383 384 385 386 387 388 389 390

	for (i = 0; i < MAX_I2C_PERIPHERALS; i++) {
		struct i2c_peripheral *i2c_dev;

		i2c_dev = &cros_laptop->i2c_peripherals[i];

		/* No more peripherals. */
		if (i2c_dev->add == NULL)
			break;

391 392 393 394 395 396 397 398 399
		if (i2c_dev->state == TIMEDOUT || i2c_dev->state == PROBED)
			continue;

		/*
		 * Check that the i2c adapter is present.
		 * -EPROBE_DEFER if missing as the adapter may appear much
		 * later.
		 */
		if (find_i2c_adapter_num(i2c_dev->type) == -ENODEV) {
400
			ret = -EPROBE_DEFER;
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
			continue;
		}

		/* Add the device. */
		if (i2c_dev->add(i2c_dev->type) == -EAGAIN) {
			/*
			 * Set -EPROBE_DEFER a limited num of times
			 * if device is not successfully added.
			 */
			if (++i2c_dev->tries < MAX_I2C_DEVICE_DEFERRALS) {
				ret = -EPROBE_DEFER;
			} else {
				/* Ran out of tries. */
				pr_notice("%s: Ran out of tries for device.\n",
					  __func__);
				i2c_dev->state = TIMEDOUT;
			}
		} else {
			i2c_dev->state = PROBED;
		}
421 422
	}

423
	return ret;
424 425
}

426
static struct chromeos_laptop samsung_series_5_550 = {
427 428 429 430 431 432 433 434
	.i2c_peripherals = {
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS },
		/* Light Sensor. */
		{ .add = setup_isl29018_als, I2C_ADAPTER_SMBUS },
	},
};

435
static struct chromeos_laptop samsung_series_5 = {
436 437 438 439 440 441
	.i2c_peripherals = {
		/* Light Sensor. */
		{ .add = setup_tsl2583_als, I2C_ADAPTER_SMBUS },
	},
};

442
static struct chromeos_laptop chromebook_pixel = {
443 444 445 446 447 448 449 450 451 452
	.i2c_peripherals = {
		/* Touch Screen. */
		{ .add = setup_atmel_1664s_ts, I2C_ADAPTER_PANEL },
		/* Touchpad. */
		{ .add = setup_atmel_224s_tp, I2C_ADAPTER_VGADDC },
		/* Light Sensor. */
		{ .add = setup_isl29018_als, I2C_ADAPTER_PANEL },
	},
};

453 454 455 456 457 458 459
static struct chromeos_laptop hp_chromebook_14 = {
	.i2c_peripherals = {
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
	},
};

460 461 462 463
static struct chromeos_laptop dell_chromebook_11 = {
	.i2c_peripherals = {
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
464 465
		/* Elan Touchpad option. */
		{ .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 },
466 467 468
	},
};

469 470 471 472 473 474 475
static struct chromeos_laptop toshiba_cb35 = {
	.i2c_peripherals = {
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
	},
};

476
static struct chromeos_laptop acer_c7_chromebook = {
477 478 479 480 481 482
	.i2c_peripherals = {
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS },
	},
};

483
static struct chromeos_laptop acer_ac700 = {
484 485 486 487 488 489
	.i2c_peripherals = {
		/* Light Sensor. */
		{ .add = setup_tsl2563_als, I2C_ADAPTER_SMBUS },
	},
};

490 491
static struct chromeos_laptop acer_c720 = {
	.i2c_peripherals = {
492 493
		/* Touchscreen. */
		{ .add = setup_atmel_1664s_ts, I2C_ADAPTER_DESIGNWARE_1 },
494 495
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
496 497
		/* Elan Touchpad option. */
		{ .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 },
498 499 500 501 502
		/* Light Sensor. */
		{ .add = setup_isl29018_als, I2C_ADAPTER_DESIGNWARE_1 },
	},
};

503
static struct chromeos_laptop hp_pavilion_14_chromebook = {
504 505 506 507 508 509
	.i2c_peripherals = {
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS },
	},
};

510
static struct chromeos_laptop cr48 = {
511 512 513 514 515 516
	.i2c_peripherals = {
		/* Light Sensor. */
		{ .add = setup_tsl2563_als, I2C_ADAPTER_SMBUS },
	},
};

517 518 519 520 521 522 523
static struct chromeos_laptop leon = {
	.i2c_peripherals = {
		/* Touchpad. */
		{ .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
	},
};

524
#define _CBDD(board_) \
525
	.callback = chromeos_laptop_dmi_matched, \
526 527
	.driver_data = (void *)&board_

528
static struct dmi_system_id chromeos_laptop_dmi_table[] __initdata = {
529
	{
530
		.ident = "Samsung Series 5 550",
531 532 533 534
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"),
		},
535
		_CBDD(samsung_series_5_550),
536
	},
537
	{
538
		.ident = "Samsung Series 5",
539
		.matches = {
540
			DMI_MATCH(DMI_PRODUCT_NAME, "Alex"),
541
		},
542
		_CBDD(samsung_series_5),
543
	},
544
	{
545
		.ident = "Chromebook Pixel",
546 547 548 549
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
		},
550
		_CBDD(chromebook_pixel),
551
	},
552 553 554 555 556 557 558 559
	{
		.ident = "Wolf",
		.matches = {
			DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Wolf"),
		},
		_CBDD(dell_chromebook_11),
	},
560 561 562 563 564 565 566 567
	{
		.ident = "HP Chromebook 14",
		.matches = {
			DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Falco"),
		},
		_CBDD(hp_chromebook_14),
	},
568 569 570 571 572 573 574 575
	{
		.ident = "Toshiba CB35",
		.matches = {
			DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Leon"),
		},
		_CBDD(toshiba_cb35),
	},
576
	{
577
		.ident = "Acer C7 Chromebook",
578 579 580
		.matches = {
			DMI_MATCH(DMI_PRODUCT_NAME, "Parrot"),
		},
581
		_CBDD(acer_c7_chromebook),
582 583
	},
	{
584
		.ident = "Acer AC700",
585
		.matches = {
586
			DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"),
587
		},
588
		_CBDD(acer_ac700),
589
	},
590 591 592 593 594 595 596
	{
		.ident = "Acer C720",
		.matches = {
			DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"),
		},
		_CBDD(acer_c720),
	},
597
	{
598
		.ident = "HP Pavilion 14 Chromebook",
599
		.matches = {
600
			DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly"),
601
		},
602
		_CBDD(hp_pavilion_14_chromebook),
603
	},
604
	{
605
		.ident = "Cr-48",
606 607 608
		.matches = {
			DMI_MATCH(DMI_PRODUCT_NAME, "Mario"),
		},
609
		_CBDD(cr48),
610
	},
611 612 613 614 615 616 617 618
	{
		.ident = "Leon",
		.matches = {
			DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Leon"),
		},
		_CBDD(leon),
	},
619 620 621 622
	{ }
};
MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table);

623 624 625 626 627 628 629 630 631
static struct platform_device *cros_platform_device;

static struct platform_driver cros_platform_driver = {
	.driver = {
		.name = "chromeos_laptop",
	},
	.probe = chromeos_laptop_probe,
};

632 633
static int __init chromeos_laptop_init(void)
{
634
	int ret;
635

636 637 638 639
	if (!dmi_check_system(chromeos_laptop_dmi_table)) {
		pr_debug("%s unsupported system.\n", __func__);
		return -ENODEV;
	}
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654

	ret = platform_driver_register(&cros_platform_driver);
	if (ret)
		return ret;

	cros_platform_device = platform_device_alloc("chromeos_laptop", -1);
	if (!cros_platform_device) {
		ret = -ENOMEM;
		goto fail_platform_device1;
	}

	ret = platform_device_add(cros_platform_device);
	if (ret)
		goto fail_platform_device2;

655
	return 0;
656 657 658 659 660 661

fail_platform_device2:
	platform_device_put(cros_platform_device);
fail_platform_device1:
	platform_driver_unregister(&cros_platform_driver);
	return ret;
662 663 664 665 666 667 668 669
}

static void __exit chromeos_laptop_exit(void)
{
	if (als)
		i2c_unregister_device(als);
	if (tp)
		i2c_unregister_device(tp);
670 671
	if (ts)
		i2c_unregister_device(ts);
672 673 674

	platform_device_unregister(cros_platform_device);
	platform_driver_unregister(&cros_platform_driver);
675 676 677 678 679 680 681 682
}

module_init(chromeos_laptop_init);
module_exit(chromeos_laptop_exit);

MODULE_DESCRIPTION("Chrome OS Laptop driver");
MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
MODULE_LICENSE("GPL");