diff options
Diffstat (limited to 'drivers/input/keyboard/atkbd.c')
| -rw-r--r-- | drivers/input/keyboard/atkbd.c | 219 | 
1 files changed, 138 insertions, 81 deletions
diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index fad04b66d268..ffde8f86e0fb 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -55,7 +55,7 @@ static int atkbd_softraw = 1;  module_param_named(softraw, atkbd_softraw, bool, 0);  MODULE_PARM_DESC(softraw, "Use software generated rawmode"); -static int atkbd_scroll = 0; +static int atkbd_scroll;  module_param_named(scroll, atkbd_scroll, bool, 0);  MODULE_PARM_DESC(scroll, "Enable scroll-wheel on MS Office and similar keyboards"); @@ -150,8 +150,8 @@ static unsigned char atkbd_unxlate_table[128] = {  #define ATKBD_RET_EMUL0		0xe0  #define ATKBD_RET_EMUL1		0xe1  #define ATKBD_RET_RELEASE	0xf0 -#define ATKBD_RET_HANGUEL	0xf1 -#define ATKBD_RET_HANJA		0xf2 +#define ATKBD_RET_HANJA		0xf1 +#define ATKBD_RET_HANGEUL	0xf2  #define ATKBD_RET_ERR		0xff  #define ATKBD_KEY_UNKNOWN	  0 @@ -170,6 +170,13 @@ static unsigned char atkbd_unxlate_table[128] = {  #define ATKBD_LED_EVENT_BIT	0  #define ATKBD_REP_EVENT_BIT	1 +#define ATKBD_XL_ERR		0x01 +#define ATKBD_XL_BAT		0x02 +#define ATKBD_XL_ACK		0x04 +#define ATKBD_XL_NAK		0x08 +#define ATKBD_XL_HANGEUL	0x10 +#define ATKBD_XL_HANJA		0x20 +  static struct {  	unsigned char keycode;  	unsigned char set2; @@ -211,8 +218,7 @@ struct atkbd {  	unsigned char emul;  	unsigned char resend;  	unsigned char release; -	unsigned char bat_xl; -	unsigned char err_xl; +	unsigned long xl_bit;  	unsigned int last;  	unsigned long time; @@ -245,17 +251,65 @@ ATKBD_DEFINE_ATTR(set);  ATKBD_DEFINE_ATTR(softrepeat);  ATKBD_DEFINE_ATTR(softraw); +static const unsigned int xl_table[] = { +	ATKBD_RET_BAT, ATKBD_RET_ERR, ATKBD_RET_ACK, +	ATKBD_RET_NAK, ATKBD_RET_HANJA, ATKBD_RET_HANGEUL, +}; -static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int code, int value) +/* + * Checks if we should mangle the scancode to extract 'release' bit + * in translated mode. + */ +static int atkbd_need_xlate(unsigned long xl_bit, unsigned char code)  { -	input_regs(dev, regs); -	if (value == 3) { -		input_report_key(dev, code, 1); -		input_sync(dev); -		input_report_key(dev, code, 0); -	} else -		input_event(dev, EV_KEY, code, value); -	input_sync(dev); +	int i; + +	if (code == ATKBD_RET_EMUL0 || code == ATKBD_RET_EMUL1) +		return 0; + +	for (i = 0; i < ARRAY_SIZE(xl_table); i++) +		if (code == xl_table[i]) +			return test_bit(i, &xl_bit); + +	return 1; +} + +/* + * Calculates new value of xl_bit so the driver can distinguish + * between make/break pair of scancodes for select keys and PS/2 + * protocol responses. + */ +static void atkbd_calculate_xl_bit(struct atkbd *atkbd, unsigned char code) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(xl_table); i++) { +		if (!((code ^ xl_table[i]) & 0x7f)) { +			if (code & 0x80) +				__clear_bit(i, &atkbd->xl_bit); +			else +				__set_bit(i, &atkbd->xl_bit); +			break; +		} +	} +} + +/* + * Encode the scancode, 0xe0 prefix, and high bit into a single integer, + * keeping kernel 2.4 compatibility for set 2 + */ +static unsigned int atkbd_compat_scancode(struct atkbd *atkbd, unsigned int code) +{ +	if (atkbd->set == 3) { +		if (atkbd->emul == 1) +			code |= 0x100; +        } else { +		code = (code & 0x7f) | ((code & 0x80) << 1); +		if (atkbd->emul == 1) +			code |= 0x80; +	} + +	return code;  }  /* @@ -267,9 +321,11 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,  			unsigned int flags, struct pt_regs *regs)  {  	struct atkbd *atkbd = serio_get_drvdata(serio); +	struct input_dev *dev = atkbd->dev;  	unsigned int code = data; -	int scroll = 0, hscroll = 0, click = -1; +	int scroll = 0, hscroll = 0, click = -1, add_release_event = 0;  	int value; +	unsigned char keycode;  #ifdef ATKBD_DEBUG  	printk(KERN_DEBUG "atkbd.c: Received %02x flags %02x\n", data, flags); @@ -298,25 +354,17 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,  	if (!atkbd->enabled)  		goto out; -	input_event(atkbd->dev, EV_MSC, MSC_RAW, code); +	input_event(dev, EV_MSC, MSC_RAW, code);  	if (atkbd->translated) { -		if (atkbd->emul || -		    (code != ATKBD_RET_EMUL0 && code != ATKBD_RET_EMUL1 && -		     code != ATKBD_RET_HANGUEL && code != ATKBD_RET_HANJA && -		     (code != ATKBD_RET_ERR || atkbd->err_xl) && -	             (code != ATKBD_RET_BAT || atkbd->bat_xl))) { +		if (atkbd->emul || atkbd_need_xlate(atkbd->xl_bit, code)) {  			atkbd->release = code >> 7;  			code &= 0x7f;  		} -		if (!atkbd->emul) { -		     if ((code & 0x7f) == (ATKBD_RET_BAT & 0x7f)) -			atkbd->bat_xl = !(data >> 7); -		     if ((code & 0x7f) == (ATKBD_RET_ERR & 0x7f)) -			atkbd->err_xl = !(data >> 7); -		} +		if (!atkbd->emul) +			atkbd_calculate_xl_bit(atkbd, data);  	}  	switch (code) { @@ -333,47 +381,48 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,  		case ATKBD_RET_RELEASE:  			atkbd->release = 1;  			goto out; -		case ATKBD_RET_HANGUEL: -			atkbd_report_key(atkbd->dev, regs, KEY_HANGUEL, 3); +		case ATKBD_RET_ACK: +		case ATKBD_RET_NAK: +			printk(KERN_WARNING "atkbd.c: Spurious %s on %s. " +			       "Some program might be trying access hardware directly.\n", +			       data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys);  			goto out; +		case ATKBD_RET_HANGEUL:  		case ATKBD_RET_HANJA: -			atkbd_report_key(atkbd->dev, regs, KEY_HANJA, 3); -			goto out; +			/* +			 * These keys do not report release and thus need to be +			 * flagged properly +			 */ +			add_release_event = 1; +			break;  		case ATKBD_RET_ERR:  			printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys);  			goto out;  	} -	if (atkbd->set != 3) -		code = (code & 0x7f) | ((code & 0x80) << 1); -	if (atkbd->emul) { -		if (--atkbd->emul) -			goto out; -		code |= (atkbd->set != 3) ? 0x80 : 0x100; -	} +	code = atkbd_compat_scancode(atkbd, code); + +	if (atkbd->emul && --atkbd->emul) +		goto out; -	if (atkbd->keycode[code] != ATKBD_KEY_NULL) -		input_event(atkbd->dev, EV_MSC, MSC_SCAN, code); +	keycode = atkbd->keycode[code]; -	switch (atkbd->keycode[code]) { +	if (keycode != ATKBD_KEY_NULL) +		input_event(dev, EV_MSC, MSC_SCAN, code); + +	switch (keycode) {  		case ATKBD_KEY_NULL:  			break;  		case ATKBD_KEY_UNKNOWN: -			if (data == ATKBD_RET_ACK || data == ATKBD_RET_NAK) { -				printk(KERN_WARNING "atkbd.c: Spurious %s on %s. Some program, " -				       "like XFree86, might be trying access hardware directly.\n", -				       data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys); -			} else { -				printk(KERN_WARNING "atkbd.c: Unknown key %s " -				       "(%s set %d, code %#x on %s).\n", -				       atkbd->release ? "released" : "pressed", -				       atkbd->translated ? "translated" : "raw", -				       atkbd->set, code, serio->phys); -				printk(KERN_WARNING "atkbd.c: Use 'setkeycodes %s%02x <keycode>' " -				       "to make it known.\n", -				       code & 0x80 ? "e0" : "", code & 0x7f); -			} -			input_sync(atkbd->dev); +			printk(KERN_WARNING +			       "atkbd.c: Unknown key %s (%s set %d, code %#x on %s).\n", +			       atkbd->release ? "released" : "pressed", +			       atkbd->translated ? "translated" : "raw", +			       atkbd->set, code, serio->phys); +			printk(KERN_WARNING +			       "atkbd.c: Use 'setkeycodes %s%02x <keycode>' to make it known.\n", +			       code & 0x80 ? "e0" : "", code & 0x7f); +			input_sync(dev);  			break;  		case ATKBD_SCR_1:  			scroll = 1 - atkbd->release * 2; @@ -397,33 +446,35 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,  			hscroll = 1;  			break;  		default: -			value = atkbd->release ? 0 : -				(1 + (!atkbd->softrepeat && test_bit(atkbd->keycode[code], atkbd->dev->key))); - -			switch (value) {	/* Workaround Toshiba laptop multiple keypress */ -				case 0: -					atkbd->last = 0; -					break; -				case 1: -					atkbd->last = code; -					atkbd->time = jiffies + msecs_to_jiffies(atkbd->dev->rep[REP_DELAY]) / 2; -					break; -				case 2: -					if (!time_after(jiffies, atkbd->time) && atkbd->last == code) -						value = 1; -					break; +			if (atkbd->release) { +				value = 0; +				atkbd->last = 0; +			} else if (!atkbd->softrepeat && test_bit(keycode, dev->key)) { +				/* Workaround Toshiba laptop multiple keypress */ +				value = time_before(jiffies, atkbd->time) && atkbd->last == code ? 1 : 2; +			} else { +				value = 1; +				atkbd->last = code; +				atkbd->time = jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]) / 2;  			} -			atkbd_report_key(atkbd->dev, regs, atkbd->keycode[code], value); +			input_regs(dev, regs); +			input_report_key(dev, keycode, value); +			input_sync(dev); + +			if (value && add_release_event) { +				input_report_key(dev, keycode, 0); +				input_sync(dev); +			}  	}  	if (atkbd->scroll) { -		input_regs(atkbd->dev, regs); +		input_regs(dev, regs);  		if (click != -1) -			input_report_key(atkbd->dev, BTN_MIDDLE, click); -		input_report_rel(atkbd->dev, REL_WHEEL, scroll); -		input_report_rel(atkbd->dev, REL_HWHEEL, hscroll); -		input_sync(atkbd->dev); +			input_report_key(dev, BTN_MIDDLE, click); +		input_report_rel(dev, REL_WHEEL, scroll); +		input_report_rel(dev, REL_HWHEEL, hscroll); +		input_sync(dev);  	}  	atkbd->release = 0; @@ -764,6 +815,9 @@ static void atkbd_set_keycode_table(struct atkbd *atkbd)  			for (i = 0; i < ARRAY_SIZE(atkbd_scroll_keys); i++)  				atkbd->keycode[atkbd_scroll_keys[i].set2] = atkbd_scroll_keys[i].keycode;  	} + +	atkbd->keycode[atkbd_compat_scancode(atkbd, ATKBD_RET_HANGEUL)] = KEY_HANGUEL; +	atkbd->keycode[atkbd_compat_scancode(atkbd, ATKBD_RET_HANJA)] = KEY_HANJA;  }  /* @@ -776,12 +830,15 @@ static void atkbd_set_device_attrs(struct atkbd *atkbd)  	int i;  	if (atkbd->extra) -		sprintf(atkbd->name, "AT Set 2 Extra keyboard"); +		snprintf(atkbd->name, sizeof(atkbd->name), +			 "AT Set 2 Extra keyboard");  	else -		sprintf(atkbd->name, "AT %s Set %d keyboard", -			atkbd->translated ? "Translated" : "Raw", atkbd->set); +		snprintf(atkbd->name, sizeof(atkbd->name), +			 "AT %s Set %d keyboard", +			 atkbd->translated ? "Translated" : "Raw", atkbd->set); -	sprintf(atkbd->phys, "%s/input0", atkbd->ps2dev.serio->phys); +	snprintf(atkbd->phys, sizeof(atkbd->phys), +		 "%s/input0", atkbd->ps2dev.serio->phys);  	input_dev->name = atkbd->name;  	input_dev->phys = atkbd->phys;  | 

