Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0d4c8597 authored by Dmitry Torokhov's avatar Dmitry Torokhov
Browse files

Input: atkbd - speed up setting leds/repeat state



Changing led state is pretty slow operation; when there are multiple
requests coming at a high rate they may interfere with normal typing.
Try optimize (skip) changing hardware state when multiple requests
are coming back-to-back.

Signed-off-by: default avatarDmitry Torokhov <dtor@mail.ru>
parent 3bedff1d
Loading
Loading
Loading
Loading
+68 −31
Original line number Diff line number Diff line
@@ -166,6 +166,9 @@ static unsigned char atkbd_unxlate_table[128] = {

#define ATKBD_SPECIAL		248

#define ATKBD_LED_EVENT_BIT	0
#define ATKBD_REP_EVENT_BIT	1

static struct {
	unsigned char keycode;
	unsigned char set2;
@@ -211,6 +214,10 @@ struct atkbd {
	unsigned char err_xl;
	unsigned int last;
	unsigned long time;

	struct work_struct event_work;
	struct semaphore event_sem;
	unsigned long event_mask;
};

static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf,
@@ -424,32 +431,31 @@ out:
}

/*
 * Event callback from the input module. Events that change the state of
 * the hardware are processed here.
 * atkbd_event_work() is used to complete processing of events that
 * can not be processed by input_event() which is often called from
 * interrupt context.
 */

static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
static void atkbd_event_work(void *data)
{
	struct atkbd *atkbd = dev->private;
	const short period[32] =
		{ 33,  37,  42,  46,  50,  54,  58,  63,  67,  75,  83,  92, 100, 109, 116, 125,
		 133, 149, 167, 182, 200, 217, 232, 250, 270, 303, 333, 370, 400, 435, 470, 500 };
	const short delay[4] =
		{ 250, 500, 750, 1000 };

	struct atkbd *atkbd = data;
	struct input_dev *dev = atkbd->dev;
	unsigned char param[2];
	int i, j;

	if (!atkbd->write)
		return -1;

	switch (type) {

		case EV_LED:
	down(&atkbd->event_sem);

	if (test_and_clear_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask)) {
		param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0)
			 | (test_bit(LED_NUML,    dev->led) ? 2 : 0)
			 | (test_bit(LED_CAPSL,   dev->led) ? 4 : 0);
		        ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS);
		ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS);

		if (atkbd->extra) {
			param[0] = 0;
@@ -458,15 +464,11 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
				 | (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0)
				 | (test_bit(LED_MISC,    dev->led) ? 0x10 : 0)
				 | (test_bit(LED_MUTE,    dev->led) ? 0x20 : 0);
				ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS);
			ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS);
		}
	}

			return 0;

		case EV_REP:

			if (atkbd->softrepeat) return 0;

	if (test_and_clear_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask)) {
		i = j = 0;
		while (i < 31 && period[i] < dev->rep[REP_PERIOD])
			i++;
@@ -475,7 +477,40 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
		dev->rep[REP_PERIOD] = period[i];
		dev->rep[REP_DELAY] = delay[j];
		param[0] = i | (j << 5);
			ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETREP);
		ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETREP);
	}

	up(&atkbd->event_sem);
}

/*
 * Event callback from the input module. Events that change the state of
 * the hardware are processed here. If action can not be performed in
 * interrupt context it is offloaded to atkbd_event_work.
 */

static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	struct atkbd *atkbd = dev->private;

	if (!atkbd->write)
		return -1;

	switch (type) {

		case EV_LED:
			set_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask);
			wmb();
			schedule_work(&atkbd->event_work);
			return 0;

		case EV_REP:

			if (!atkbd->softrepeat) {
				set_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask);
				wmb();
				schedule_work(&atkbd->event_work);
			}

			return 0;
	}
@@ -810,6 +845,8 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)

	atkbd->dev = dev;
	ps2_init(&atkbd->ps2dev, serio);
	INIT_WORK(&atkbd->event_work, atkbd_event_work, atkbd);
	init_MUTEX(&atkbd->event_sem);

	switch (serio->id.type) {