compal-laptop.c 29 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
26
/*-*-linux-c-*-*/

/*
  Copyright (C) 2008 Cezary Jackiewicz <cezary.jackiewicz (at) gmail.com>

  based on MSI driver

  Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>

  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., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.
 */

/*
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 * compal-laptop.c - Compal laptop support.
 *
 * This driver exports a few files in /sys/devices/platform/compal-laptop/:
 *   wake_up_XXX   Whether or not we listen to such wake up events (rw)
 *
 * In addition to these platform device attributes the driver
 * registers itself in the Linux backlight control, power_supply, rfkill
 * and hwmon subsystem and is available to userspace under:
 *
 *   /sys/class/backlight/compal-laptop/
 *   /sys/class/power_supply/compal-laptop/
 *   /sys/class/rfkill/rfkillX/
 *   /sys/class/hwmon/hwmonX/
 *
 * Notes on the power_supply battery interface:
 *   - the "minimum" design voltage is *the* design voltage
 *   - the ambient temperature is the average battery temperature
 *     and the value is an educated guess (see commented code below)
45
46
47
48
49
 *
 *
 * This driver might work on other laptops produced by Compal. If you
 * want to try it you can pass force=1 as argument to the module which
 * will force it to load even when the DMI data doesn't identify the
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
 * laptop as compatible.
 *
 * Lots of data available at:
 * http://service1.marasst.com/Compal/JHL90_91/Service%20Manual/
 * JHL90%20service%20manual-Final-0725.pdf
 *
 *
 *
 * Support for the Compal JHL90 added by Roald Frederickx
 * (roald.frederickx@gmail.com):
 * Driver got large revision. Added functionalities: backlight
 * power, wake_on_XXX, a hwmon and power_supply interface.
 *
 * In case this gets merged into the kernel source: I want to dedicate this
 * to Kasper Meerts, the awesome guy who showed me Linux and C!
65
66
 */

67
68
69
70
/* NOTE: currently the wake_on_XXX, hwmon and power_supply interfaces are
 * only enabled on a JHL90 board until it is verified that they work on the
 * other boards too.  See the extra_features variable. */

71
72
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

73
74
75
76
77
78
79
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/backlight.h>
#include <linux/platform_device.h>
80
#include <linux/rfkill.h>
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/power_supply.h>
#include <linux/fb.h>


/* ======= */
/* Defines */
/* ======= */
#define DRIVER_NAME "compal-laptop"
#define DRIVER_VERSION	"0.2.7"

#define BACKLIGHT_LEVEL_ADDR		0xB9
#define BACKLIGHT_LEVEL_MAX		7
#define BACKLIGHT_STATE_ADDR		0x59
#define BACKLIGHT_STATE_ON_DATA		0xE1
#define BACKLIGHT_STATE_OFF_DATA	0xE2

#define WAKE_UP_ADDR			0xA4
#define WAKE_UP_PME			(1 << 0)
#define WAKE_UP_MODEM			(1 << 1)
#define WAKE_UP_LAN			(1 << 2)
#define WAKE_UP_WLAN			(1 << 4)
#define WAKE_UP_KEY			(1 << 6)
#define WAKE_UP_MOUSE			(1 << 7)

#define WIRELESS_ADDR			0xBB
#define WIRELESS_WLAN			(1 << 0)
#define WIRELESS_BT			(1 << 1)
#define WIRELESS_WLAN_EXISTS		(1 << 2)
#define WIRELESS_BT_EXISTS		(1 << 3)
#define WIRELESS_KILLSWITCH		(1 << 4)

#define PWM_ADDRESS			0x46
#define PWM_DISABLE_ADDR		0x59
#define PWM_DISABLE_DATA		0xA5
#define PWM_ENABLE_ADDR			0x59
#define PWM_ENABLE_DATA			0xA8

#define FAN_ADDRESS			0x46
#define FAN_DATA			0x81
#define FAN_FULL_ON_CMD			0x59 /* Doesn't seem to work. Just */
#define FAN_FULL_ON_ENABLE		0x76 /* force the pwm signal to its */
#define FAN_FULL_ON_DISABLE		0x77 /* maximum value instead */

#define TEMP_CPU			0xB0
#define TEMP_CPU_LOCAL			0xB1
#define TEMP_CPU_DTS			0xB5
#define TEMP_NORTHBRIDGE		0xB6
#define TEMP_VGA			0xB4
#define TEMP_SKIN			0xB2

#define BAT_MANUFACTURER_NAME_ADDR	0x10
#define BAT_MANUFACTURER_NAME_LEN	9
#define BAT_MODEL_NAME_ADDR		0x19
#define BAT_MODEL_NAME_LEN		6
#define BAT_SERIAL_NUMBER_ADDR		0xC4
#define BAT_SERIAL_NUMBER_LEN		5
#define BAT_CHARGE_NOW			0xC2
#define BAT_CHARGE_DESIGN		0xCA
#define BAT_VOLTAGE_NOW			0xC6
#define BAT_VOLTAGE_DESIGN		0xC8
#define BAT_CURRENT_NOW			0xD0
#define BAT_CURRENT_AVG			0xD2
#define BAT_POWER			0xD4
#define BAT_CAPACITY			0xCE
#define BAT_TEMP			0xD6
#define BAT_TEMP_AVG			0xD7
#define BAT_STATUS0			0xC1
#define BAT_STATUS1			0xF0
#define BAT_STATUS2			0xF1
#define BAT_STOP_CHARGE1		0xF2
#define BAT_STOP_CHARGE2		0xF3

#define BAT_S0_DISCHARGE		(1 << 0)
#define BAT_S0_DISCHRG_CRITICAL		(1 << 2)
#define BAT_S0_LOW			(1 << 3)
#define BAT_S0_CHARGING			(1 << 1)
#define BAT_S0_AC			(1 << 7)
#define BAT_S1_EXISTS			(1 << 0)
#define BAT_S1_FULL			(1 << 1)
#define BAT_S1_EMPTY			(1 << 2)
#define BAT_S1_LiION_OR_NiMH		(1 << 7)
#define BAT_S2_LOW_LOW			(1 << 0)
#define BAT_STOP_CHRG1_BAD_CELL		(1 << 1)
#define BAT_STOP_CHRG1_COMM_FAIL	(1 << 2)
#define BAT_STOP_CHRG1_OVERVOLTAGE	(1 << 6)
#define BAT_STOP_CHRG1_OVERTEMPERATURE	(1 << 7)


/* ======= */
/* Structs */
/* ======= */
struct compal_data{
	/* Fan control */
176
	int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by motherboard */
177
178
179
180
181
182
183
184
185
	unsigned char curr_pwm;

	/* Power supply */
	struct power_supply psy;
	struct power_supply_info psy_info;
	char bat_model_name[BAT_MODEL_NAME_LEN + 1];
	char bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN + 1];
	char bat_serial_number[BAT_SERIAL_NUMBER_LEN + 1];
};
186
187


188
189
190
/* =============== */
/* General globals */
/* =============== */
191
static bool force;
192
193
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
194

195
196
197
198
199
200
201
202
203
/* Support for the wake_on_XXX, hwmon and power_supply interface. Currently
 * only gets enabled on a JHL90 board. Might work with the others too */
static bool extra_features;

/* Nasty stuff. For some reason the fan control is very un-linear.  I've
 * come up with these values by looping through the possible inputs and
 * watching the output of address 0x4F (do an ec_transaction writing 0x33
 * into 0x4F and read a few bytes from the output, like so:
 *	u8 writeData = 0x33;
Thomas Renninger's avatar
Thomas Renninger committed
204
 *	ec_transaction(0x4F, &writeData, 1, buffer, 32);
205
 * That address is labeled "fan1 table information" in the service manual.
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
 * It should be clear which value in 'buffer' changes). This seems to be
 * related to fan speed. It isn't a proper 'realtime' fan speed value
 * though, because physically stopping or speeding up the fan doesn't
 * change it. It might be the average voltage or current of the pwm output.
 * Nevertheless, it is more fine-grained than the actual RPM reading */
static const unsigned char pwm_lookup_table[256] = {
	0, 0, 0, 1, 1, 1, 2, 253, 254, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6,
	7, 7, 7, 8, 86, 86, 9, 9, 9, 10, 10, 10, 11, 92, 92, 12, 12, 95,
	13, 66, 66, 14, 14, 98, 15, 15, 15, 16, 16, 67, 17, 17, 72, 18, 70,
	75, 19, 90, 90, 73, 73, 73, 21, 21, 91, 91, 91, 96, 23, 94, 94, 94,
	94, 94, 94, 94, 94, 94, 94, 141, 141, 238, 223, 192, 139, 139, 139,
	139, 139, 142, 142, 142, 142, 142, 78, 78, 78, 78, 78, 76, 76, 76,
	76, 76, 79, 79, 79, 79, 79, 79, 79, 20, 20, 20, 20, 20, 22, 22, 22,
	22, 22, 24, 24, 24, 24, 24, 24, 219, 219, 219, 219, 219, 219, 219,
	219, 27, 27, 188, 188, 28, 28, 28, 29, 186, 186, 186, 186, 186,
	186, 186, 186, 186, 186, 31, 31, 31, 31, 31, 32, 32, 32, 41, 33,
	33, 33, 33, 33, 252, 252, 34, 34, 34, 43, 35, 35, 35, 36, 36, 38,
	206, 206, 206, 206, 206, 206, 206, 206, 206, 37, 37, 37, 46, 46,
	47, 47, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 48, 48,
	48, 48, 48, 40, 40, 40, 49, 42, 42, 42, 42, 42, 42, 42, 42, 44,
	189, 189, 189, 189, 54, 54, 45, 45, 45, 45, 45, 45, 45, 45, 251,
	191, 199, 199, 199, 199, 199, 215, 215, 215, 215, 187, 187, 187,
	187, 187, 193, 50
};
230
231


232

233

234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
/* ========================= */
/* Hardware access functions */
/* ========================= */
/* General access */
static u8 ec_read_u8(u8 addr)
{
	u8 value;
	ec_read(addr, &value);
	return value;
}

static s8 ec_read_s8(u8 addr)
{
	return (s8)ec_read_u8(addr);
}

static u16 ec_read_u16(u8 addr)
{
	int hi, lo;
	lo = ec_read_u8(addr);
	hi = ec_read_u8(addr + 1);
	return (hi << 8) + lo;
}

static s16 ec_read_s16(u8 addr)
{
	return (s16) ec_read_u16(addr);
}
262

263
static void ec_read_sequence(u8 addr, u8 *buf, int len)
264
{
265
266
267
268
269
270
271
272
273
274
	int i;
	for (i = 0; i < len; i++)
		ec_read(addr + i, buf + i);
}


/* Backlight access */
static int set_backlight_level(int level)
{
	if (level < 0 || level > BACKLIGHT_LEVEL_MAX)
275
276
		return -EINVAL;

277
	ec_write(BACKLIGHT_LEVEL_ADDR, level);
278

279
	return 0;
280
281
}

282
283
284
285
286
287
288
289
static int get_backlight_level(void)
{
	return (int) ec_read_u8(BACKLIGHT_LEVEL_ADDR);
}

static void set_backlight_state(bool on)
{
	u8 data = on ? BACKLIGHT_STATE_ON_DATA : BACKLIGHT_STATE_OFF_DATA;
Thomas Renninger's avatar
Thomas Renninger committed
290
	ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0);
291
292
293
294
295
296
297
}


/* Fan control access */
static void pwm_enable_control(void)
{
	unsigned char writeData = PWM_ENABLE_DATA;
Thomas Renninger's avatar
Thomas Renninger committed
298
	ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0);
299
300
301
}

static void pwm_disable_control(void)
302
{
303
	unsigned char writeData = PWM_DISABLE_DATA;
Thomas Renninger's avatar
Thomas Renninger committed
304
	ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0);
305
}
306

307
308
static void set_pwm(int pwm)
{
Thomas Renninger's avatar
Thomas Renninger committed
309
	ec_transaction(PWM_ADDRESS, &pwm_lookup_table[pwm], 1, NULL, 0);
310
311
312
313
314
}

static int get_fan_rpm(void)
{
	u8 value, data = FAN_DATA;
Thomas Renninger's avatar
Thomas Renninger committed
315
	ec_transaction(FAN_ADDRESS, &data, 1, &value, 1);
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
	return 100 * (int)value;
}




/* =================== */
/* Interface functions */
/* =================== */

/* Backlight interface */
static int bl_get_brightness(struct backlight_device *b)
{
	return get_backlight_level();
}

static int bl_update_status(struct backlight_device *b)
{
	int ret = set_backlight_level(b->props.brightness);
	if (ret)
		return ret;
337

338
339
340
341
	set_backlight_state((b->props.power == FB_BLANK_UNBLANK)
		&&    !(b->props.state & BL_CORE_SUSPENDED)
		&&    !(b->props.state & BL_CORE_FBBLANK));
	return 0;
342
343
}

344
345
346
347
348
349
350
static const struct backlight_ops compalbl_ops = {
	.get_brightness = bl_get_brightness,
	.update_status	= bl_update_status,
};


/* Wireless interface */
351
static int compal_rfkill_set(void *data, bool blocked)
352
{
353
	unsigned long radio = (unsigned long) data;
354
355
	u8 result = ec_read_u8(WIRELESS_ADDR);
	u8 value;
356

357
358
359
360
	if (!blocked)
		value = (u8) (result | radio);
	else
		value = (u8) (result & ~radio);
361
	ec_write(WIRELESS_ADDR, value);
362
363
364
365

	return 0;
}

366
static void compal_rfkill_poll(struct rfkill *rfkill, void *data)
367
{
368
369
	u8 result = ec_read_u8(WIRELESS_ADDR);
	bool hw_blocked = !(result & WIRELESS_KILLSWITCH);
370
	rfkill_set_hw_state(rfkill, hw_blocked);
371
372
}

373
374
375
376
377
static const struct rfkill_ops compal_rfkill_ops = {
	.poll = compal_rfkill_poll,
	.set_block = compal_rfkill_set,
};

378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410

/* Wake_up interface */
#define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK)			\
static ssize_t NAME##_show(struct device *dev,				\
	struct device_attribute *attr, char *buf)			\
{									\
	return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0));	\
}									\
static ssize_t NAME##_store(struct device *dev,				\
	struct device_attribute *attr, const char *buf, size_t count)	\
{									\
	int state;							\
	u8 old_val = ec_read_u8(ADDR);					\
	if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1))	\
		return -EINVAL;						\
	ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK));	\
	return count;							\
}

SIMPLE_MASKED_STORE_SHOW(wake_up_pme,	WAKE_UP_ADDR, WAKE_UP_PME)
SIMPLE_MASKED_STORE_SHOW(wake_up_modem,	WAKE_UP_ADDR, WAKE_UP_MODEM)
SIMPLE_MASKED_STORE_SHOW(wake_up_lan,	WAKE_UP_ADDR, WAKE_UP_LAN)
SIMPLE_MASKED_STORE_SHOW(wake_up_wlan,	WAKE_UP_ADDR, WAKE_UP_WLAN)
SIMPLE_MASKED_STORE_SHOW(wake_up_key,	WAKE_UP_ADDR, WAKE_UP_KEY)
SIMPLE_MASKED_STORE_SHOW(wake_up_mouse,	WAKE_UP_ADDR, WAKE_UP_MOUSE)

/* Fan control interface */
static ssize_t pwm_enable_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct compal_data *data = dev_get_drvdata(dev);
	return sprintf(buf, "%d\n", data->pwm_enable);
}
411

412
413
414
415
416
417
static ssize_t pwm_enable_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct compal_data *data = dev_get_drvdata(dev);
	long val;
	int err;
418
419

	err = kstrtol(buf, 10, &val);
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
	if (err)
		return err;
	if (val < 0)
		return -EINVAL;

	data->pwm_enable = val;

	switch (val) {
	case 0:  /* Full speed */
		pwm_enable_control();
		set_pwm(255);
		break;
	case 1:  /* As set by pwm1 */
		pwm_enable_control();
		set_pwm(data->curr_pwm);
		break;
	default: /* Control by motherboard */
		pwm_disable_control();
		break;
439
440
	}

441
442
	return count;
}
443

444
445
446
447
448
449
static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	struct compal_data *data = dev_get_drvdata(dev);
	return sprintf(buf, "%hhu\n", data->curr_pwm);
}
450

451
452
453
454
455
456
static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct compal_data *data = dev_get_drvdata(dev);
	long val;
	int err;
457
458

	err = kstrtol(buf, 10, &val);
459
460
461
462
	if (err)
		return err;
	if (val < 0 || val > 255)
		return -EINVAL;
463

464
	data->curr_pwm = val;
465

466
467
468
469
470
	if (data->pwm_enable != 1)
		return count;
	set_pwm(val);

	return count;
471
472
}

473
474
475
476
477
static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	return sprintf(buf, "%d\n", get_fan_rpm());
}
478

479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503

/* Temperature interface */
#define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL)	\
static ssize_t temp_##POSTFIX(struct device *dev,			\
		struct device_attribute *attr, char *buf)		\
{									\
	return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS));	\
}									\
static ssize_t label_##POSTFIX(struct device *dev,			\
		struct device_attribute *attr, char *buf)		\
{									\
	return sprintf(buf, "%s\n", LABEL);				\
}

/* Labels as in service guide */
TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu,        TEMP_CPU,        "CPU_TEMP");
TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_local,  TEMP_CPU_LOCAL,  "CPU_TEMP_LOCAL");
TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_DTS,    TEMP_CPU_DTS,    "CPU_DTS");
TEMPERATURE_SHOW_TEMP_AND_LABEL(northbridge,TEMP_NORTHBRIDGE,"NorthBridge");
TEMPERATURE_SHOW_TEMP_AND_LABEL(vga,        TEMP_VGA,        "VGA_TEMP");
TEMPERATURE_SHOW_TEMP_AND_LABEL(SKIN,       TEMP_SKIN,       "SKIN_TEMP90");


/* Power supply interface */
static int bat_status(void)
504
{
505
506
507
508
509
510
511
512
513
514
	u8 status0 = ec_read_u8(BAT_STATUS0);
	u8 status1 = ec_read_u8(BAT_STATUS1);

	if (status0 & BAT_S0_CHARGING)
		return POWER_SUPPLY_STATUS_CHARGING;
	if (status0 & BAT_S0_DISCHARGE)
		return POWER_SUPPLY_STATUS_DISCHARGING;
	if (status1 & BAT_S1_FULL)
		return POWER_SUPPLY_STATUS_FULL;
	return POWER_SUPPLY_STATUS_NOT_CHARGING;
515
516
}

517
518
519
520
521
522
523
524
525
526
527
528
529
530
static int bat_health(void)
{
	u8 status = ec_read_u8(BAT_STOP_CHARGE1);

	if (status & BAT_STOP_CHRG1_OVERTEMPERATURE)
		return POWER_SUPPLY_HEALTH_OVERHEAT;
	if (status & BAT_STOP_CHRG1_OVERVOLTAGE)
		return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
	if (status & BAT_STOP_CHRG1_BAD_CELL)
		return POWER_SUPPLY_HEALTH_DEAD;
	if (status & BAT_STOP_CHRG1_COMM_FAIL)
		return POWER_SUPPLY_HEALTH_UNKNOWN;
	return POWER_SUPPLY_HEALTH_GOOD;
}
531

532
static int bat_is_present(void)
533
{
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
	u8 status = ec_read_u8(BAT_STATUS2);
	return ((status & BAT_S1_EXISTS) != 0);
}

static int bat_technology(void)
{
	u8 status = ec_read_u8(BAT_STATUS1);

	if (status & BAT_S1_LiION_OR_NiMH)
		return POWER_SUPPLY_TECHNOLOGY_LION;
	return POWER_SUPPLY_TECHNOLOGY_NiMH;
}

static int bat_capacity_level(void)
{
	u8 status0 = ec_read_u8(BAT_STATUS0);
	u8 status1 = ec_read_u8(BAT_STATUS1);
	u8 status2 = ec_read_u8(BAT_STATUS2);

	if (status0 & BAT_S0_DISCHRG_CRITICAL
			|| status1 & BAT_S1_EMPTY
			|| status2 & BAT_S2_LOW_LOW)
		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
	if (status0 & BAT_S0_LOW)
		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
	if (status1 & BAT_S1_FULL)
		return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
	return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
}

static int bat_get_property(struct power_supply *psy,
				enum power_supply_property psp,
				union power_supply_propval *val)
{
	struct compal_data *data;
	data = container_of(psy, struct compal_data, psy);

	switch (psp) {
	case POWER_SUPPLY_PROP_STATUS:
		val->intval = bat_status();
		break;
	case POWER_SUPPLY_PROP_HEALTH:
		val->intval = bat_health();
		break;
	case POWER_SUPPLY_PROP_PRESENT:
		val->intval = bat_is_present();
		break;
	case POWER_SUPPLY_PROP_TECHNOLOGY:
		val->intval = bat_technology();
		break;
	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: /* THE design voltage... */
		val->intval = ec_read_u16(BAT_VOLTAGE_DESIGN) * 1000;
		break;
	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
		val->intval = ec_read_u16(BAT_VOLTAGE_NOW) * 1000;
		break;
	case POWER_SUPPLY_PROP_CURRENT_NOW:
		val->intval = ec_read_s16(BAT_CURRENT_NOW) * 1000;
		break;
	case POWER_SUPPLY_PROP_CURRENT_AVG:
		val->intval = ec_read_s16(BAT_CURRENT_AVG) * 1000;
		break;
	case POWER_SUPPLY_PROP_POWER_NOW:
		val->intval = ec_read_u8(BAT_POWER) * 1000000;
		break;
	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
		val->intval = ec_read_u16(BAT_CHARGE_DESIGN) * 1000;
		break;
	case POWER_SUPPLY_PROP_CHARGE_NOW:
		val->intval = ec_read_u16(BAT_CHARGE_NOW) * 1000;
		break;
	case POWER_SUPPLY_PROP_CAPACITY:
		val->intval = ec_read_u8(BAT_CAPACITY);
		break;
	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
		val->intval = bat_capacity_level();
		break;
	/* It smees that BAT_TEMP_AVG is a (2's complement?) value showing
	 * the number of degrees, whereas BAT_TEMP is somewhat more
	 * complicated. It looks like this is a negative nember with a
	 * 100/256 divider and an offset of 222. Both were determined
	 * experimentally by comparing BAT_TEMP and BAT_TEMP_AVG. */
	case POWER_SUPPLY_PROP_TEMP:
		val->intval = ((222 - (int)ec_read_u8(BAT_TEMP)) * 1000) >> 8;
		break;
	case POWER_SUPPLY_PROP_TEMP_AMBIENT: /* Ambient, Avg, ... same thing */
		val->intval = ec_read_s8(BAT_TEMP_AVG) * 10;
		break;
	/* Neither the model name nor manufacturer name work for me. */
	case POWER_SUPPLY_PROP_MODEL_NAME:
		val->strval = data->bat_model_name;
		break;
	case POWER_SUPPLY_PROP_MANUFACTURER:
		val->strval = data->bat_manufacturer_name;
		break;
	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
		val->strval = data->bat_serial_number;
		break;
	default:
		break;
	}
	return 0;
636
637
638
639
640
}




641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657

/* ============== */
/* Driver Globals */
/* ============== */
static DEVICE_ATTR(wake_up_pme,
		0644, wake_up_pme_show,		wake_up_pme_store);
static DEVICE_ATTR(wake_up_modem,
		0644, wake_up_modem_show,	wake_up_modem_store);
static DEVICE_ATTR(wake_up_lan,
		0644, wake_up_lan_show,	wake_up_lan_store);
static DEVICE_ATTR(wake_up_wlan,
		0644, wake_up_wlan_show,	wake_up_wlan_store);
static DEVICE_ATTR(wake_up_key,
		0644, wake_up_key_show,	wake_up_key_store);
static DEVICE_ATTR(wake_up_mouse,
		0644, wake_up_mouse_show,	wake_up_mouse_store);

658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
static DEVICE_ATTR(fan1_input,  S_IRUGO, fan_show,          NULL);
static DEVICE_ATTR(temp1_input, S_IRUGO, temp_cpu,          NULL);
static DEVICE_ATTR(temp2_input, S_IRUGO, temp_cpu_local,    NULL);
static DEVICE_ATTR(temp3_input, S_IRUGO, temp_cpu_DTS,      NULL);
static DEVICE_ATTR(temp4_input, S_IRUGO, temp_northbridge,  NULL);
static DEVICE_ATTR(temp5_input, S_IRUGO, temp_vga,          NULL);
static DEVICE_ATTR(temp6_input, S_IRUGO, temp_SKIN,         NULL);
static DEVICE_ATTR(temp1_label, S_IRUGO, label_cpu,         NULL);
static DEVICE_ATTR(temp2_label, S_IRUGO, label_cpu_local,   NULL);
static DEVICE_ATTR(temp3_label, S_IRUGO, label_cpu_DTS,     NULL);
static DEVICE_ATTR(temp4_label, S_IRUGO, label_northbridge, NULL);
static DEVICE_ATTR(temp5_label, S_IRUGO, label_vga,         NULL);
static DEVICE_ATTR(temp6_label, S_IRUGO, label_SKIN,        NULL);
static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, pwm_show, pwm_store);
static DEVICE_ATTR(pwm1_enable,
		   S_IRUGO | S_IWUSR, pwm_enable_show, pwm_enable_store);
674

675
static struct attribute *compal_platform_attrs[] = {
676
677
678
679
680
681
	&dev_attr_wake_up_pme.attr,
	&dev_attr_wake_up_modem.attr,
	&dev_attr_wake_up_lan.attr,
	&dev_attr_wake_up_wlan.attr,
	&dev_attr_wake_up_key.attr,
	&dev_attr_wake_up_mouse.attr,
682
683
684
685
686
687
688
	NULL
};
static struct attribute_group compal_platform_attr_group = {
	.attrs = compal_platform_attrs
};

static struct attribute *compal_hwmon_attrs[] = {
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
	&dev_attr_pwm1_enable.attr,
	&dev_attr_pwm1.attr,
	&dev_attr_fan1_input.attr,
	&dev_attr_temp1_input.attr,
	&dev_attr_temp2_input.attr,
	&dev_attr_temp3_input.attr,
	&dev_attr_temp4_input.attr,
	&dev_attr_temp5_input.attr,
	&dev_attr_temp6_input.attr,
	&dev_attr_temp1_label.attr,
	&dev_attr_temp2_label.attr,
	&dev_attr_temp3_label.attr,
	&dev_attr_temp4_label.attr,
	&dev_attr_temp5_label.attr,
	&dev_attr_temp6_label.attr,
704
705
	NULL
};
706
ATTRIBUTE_GROUPS(compal_hwmon);
707

708
709
static int compal_probe(struct platform_device *);
static int compal_remove(struct platform_device *);
710
711
static struct platform_driver compal_driver = {
	.driver = {
712
		.name = DRIVER_NAME,
713
		.owner = THIS_MODULE,
714
715
	},
	.probe	= compal_probe,
716
	.remove	= compal_remove,
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
};

static enum power_supply_property compal_bat_properties[] = {
	POWER_SUPPLY_PROP_STATUS,
	POWER_SUPPLY_PROP_HEALTH,
	POWER_SUPPLY_PROP_PRESENT,
	POWER_SUPPLY_PROP_TECHNOLOGY,
	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
	POWER_SUPPLY_PROP_VOLTAGE_NOW,
	POWER_SUPPLY_PROP_CURRENT_NOW,
	POWER_SUPPLY_PROP_CURRENT_AVG,
	POWER_SUPPLY_PROP_POWER_NOW,
	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
	POWER_SUPPLY_PROP_CHARGE_NOW,
	POWER_SUPPLY_PROP_CAPACITY,
	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
	POWER_SUPPLY_PROP_TEMP,
	POWER_SUPPLY_PROP_TEMP_AMBIENT,
	POWER_SUPPLY_PROP_MODEL_NAME,
	POWER_SUPPLY_PROP_MANUFACTURER,
	POWER_SUPPLY_PROP_SERIAL_NUMBER,
738
739
};

740
741
742
743
744
745
746
747
748
749
750
751
752
753
static struct backlight_device *compalbl_device;

static struct platform_device *compal_device;

static struct rfkill *wifi_rfkill;
static struct rfkill *bt_rfkill;





/* =================================== */
/* Initialization & clean-up functions */
/* =================================== */
754
755
756

static int dmi_check_cb(const struct dmi_system_id *id)
{
757
	pr_info("Identified laptop model '%s'\n", id->ident);
758
	extra_features = false;
759
	return 1;
760
}
761

762
763
static int dmi_check_cb_extra(const struct dmi_system_id *id)
{
764
	pr_info("Identified laptop model '%s', enabling extra features\n",
765
766
		id->ident);
	extra_features = true;
767
	return 1;
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
}

static struct dmi_system_id __initdata compal_dmi_table[] = {
	{
		.ident = "FL90/IFL90",
		.matches = {
			DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
		},
		.callback = dmi_check_cb
	},
	{
		.ident = "FL90/IFL90",
		.matches = {
			DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
			DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
		},
		.callback = dmi_check_cb
	},
	{
		.ident = "FL91/IFL91",
		.matches = {
			DMI_MATCH(DMI_BOARD_NAME, "IFL91"),
			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
		},
		.callback = dmi_check_cb
	},
	{
		.ident = "FL92/JFL92",
		.matches = {
			DMI_MATCH(DMI_BOARD_NAME, "JFL92"),
			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
		},
		.callback = dmi_check_cb
	},
	{
		.ident = "FT00/IFT00",
		.matches = {
			DMI_MATCH(DMI_BOARD_NAME, "IFT00"),
			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
		},
		.callback = dmi_check_cb
	},
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
	{
		.ident = "Dell Mini 9",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"),
		},
		.callback = dmi_check_cb
	},
	{
		.ident = "Dell Mini 10",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"),
		},
		.callback = dmi_check_cb
	},
	{
		.ident = "Dell Mini 10v",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"),
		},
		.callback = dmi_check_cb
	},
835
836
837
838
839
840
841
842
	{
		.ident = "Dell Mini 1012",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"),
		},
		.callback = dmi_check_cb
	},
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
	{
		.ident = "Dell Inspiron 11z",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"),
		},
		.callback = dmi_check_cb
	},
	{
		.ident = "Dell Mini 12",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"),
		},
		.callback = dmi_check_cb
	},
859
860
861
862
863
864
865
866
	{
		.ident = "JHL90",
		.matches = {
			DMI_MATCH(DMI_BOARD_NAME, "JHL90"),
			DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
		},
		.callback = dmi_check_cb_extra
	},
867
868
869
870
871
872
873
874
	{
		.ident = "KHLB2",
		.matches = {
			DMI_MATCH(DMI_BOARD_NAME, "KHLB2"),
			DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
		},
		.callback = dmi_check_cb_extra
	},
875
876
	{ }
};
877
MODULE_DEVICE_TABLE(dmi, compal_dmi_table);
878

879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
static void initialize_power_supply_data(struct compal_data *data)
{
	data->psy.name = DRIVER_NAME;
	data->psy.type = POWER_SUPPLY_TYPE_BATTERY;
	data->psy.properties = compal_bat_properties;
	data->psy.num_properties = ARRAY_SIZE(compal_bat_properties);
	data->psy.get_property = bat_get_property;

	ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR,
					data->bat_manufacturer_name,
					BAT_MANUFACTURER_NAME_LEN);
	data->bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN] = 0;

	ec_read_sequence(BAT_MODEL_NAME_ADDR,
					data->bat_model_name,
					BAT_MODEL_NAME_LEN);
	data->bat_model_name[BAT_MODEL_NAME_LEN] = 0;

	scnprintf(data->bat_serial_number, BAT_SERIAL_NUMBER_LEN + 1, "%d",
				ec_read_u16(BAT_SERIAL_NUMBER_ADDR));
}

static void initialize_fan_control_data(struct compal_data *data)
{
	data->pwm_enable = 2; /* Keep motherboard in control for now */
	data->curr_pwm = 255; /* Try not to cause a CPU_on_fire exception
				 if we take over... */
}

static int setup_rfkill(void)
{
	int ret;

	wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev,
				RFKILL_TYPE_WLAN, &compal_rfkill_ops,
				(void *) WIRELESS_WLAN);
	if (!wifi_rfkill)
		return -ENOMEM;

	ret = rfkill_register(wifi_rfkill);
	if (ret)
		goto err_wifi;

	bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev,
				RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops,
				(void *) WIRELESS_BT);
	if (!bt_rfkill) {
		ret = -ENOMEM;
		goto err_allocate_bt;
	}
	ret = rfkill_register(bt_rfkill);
	if (ret)
		goto err_register_bt;

	return 0;

err_register_bt:
	rfkill_destroy(bt_rfkill);

err_allocate_bt:
	rfkill_unregister(wifi_rfkill);

err_wifi:
	rfkill_destroy(wifi_rfkill);

	return ret;
}

947
948
949
950
static int __init compal_init(void)
{
	int ret;

951
	if (acpi_disabled) {
952
		pr_err("ACPI needs to be enabled for this driver to work!\n");
953
		return -ENODEV;
954
	}
955

956
	if (!force && !dmi_check_system(compal_dmi_table)) {
957
		pr_err("Motherboard not recognized (You could try the module's force-parameter)\n");
958
		return -ENODEV;
959
	}
960

961
	if (!acpi_video_backlight_support()) {
962
963
		struct backlight_properties props;
		memset(&props, 0, sizeof(struct backlight_properties));
964
		props.type = BACKLIGHT_PLATFORM;
965
966
		props.max_brightness = BACKLIGHT_LEVEL_MAX;
		compalbl_device = backlight_device_register(DRIVER_NAME,
967
968
969
							    NULL, NULL,
							    &compalbl_ops,
							    &props);
970
971
972
		if (IS_ERR(compalbl_device))
			return PTR_ERR(compalbl_device);
	}
973
974
975

	ret = platform_driver_register(&compal_driver);
	if (ret)
976
		goto err_backlight;
977

978
	compal_device = platform_device_alloc(DRIVER_NAME, -1);
979
980
	if (!compal_device) {
		ret = -ENOMEM;
981
		goto err_platform_driver;
982
983
	}

984
	ret = platform_device_add(compal_device); /* This calls compal_probe */
985
	if (ret)
986
		goto err_platform_device;
987

988
	ret = setup_rfkill();
989
	if (ret)
990
		goto err_rfkill;
991

992
	pr_info("Driver " DRIVER_VERSION " successfully loaded\n");
993
994
	return 0;

995
err_rfkill:
996
997
	platform_device_del(compal_device);

998
err_platform_device:
999
1000
	platform_device_put(compal_device);

For faster browsing, not all history is shown. View entire blame