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

Commit 1549ee6f authored by malattia@linux.it's avatar malattia@linux.it Committed by Len Brown
Browse files

sony-laptop: Unify the input subsystem event forwarding



SNC and SPIC events are forwarded to the same input devices
and are thus handled together.

Signed-off-by: default avatarMattia Dongili <malattia@linux.it>
Signed-off-by: default avatarLen Brown <len.brown@intel.com>
parent 33a04454
Loading
Loading
Loading
Loading
+276 −265
Original line number Diff line number Diff line
@@ -97,18 +97,266 @@ MODULE_PARM_DESC(no_spic,
static int compat;		/* = 0 */
module_param(compat, int, 0444);
MODULE_PARM_DESC(compat,
		 "set this if you want to enable backward compatibility mode");

static int force_jog;		/* = 0 */
module_param(force_jog, int, 0444);
MODULE_PARM_DESC(force_jog,
		 "set this if the driver doesn't detect your jogdial");
		 "set this if you want to enable backward compatibility mode for SPIC");

static unsigned long mask = 0xffffffff;
module_param(mask, ulong, 0644);
MODULE_PARM_DESC(mask,
		 "set this to the mask of event you want to enable (see doc)");

/*********** Input Devices ***********/

#define SONY_LAPTOP_BUF_SIZE	128
struct sony_laptop_input_s {
	atomic_t		users;
	struct input_dev	*jog_dev;
	struct input_dev	*key_dev;
	struct kfifo		*fifo;
	spinlock_t		fifo_lock;
	struct workqueue_struct	*wq;
};
static struct sony_laptop_input_s sony_laptop_input = {
	.users = ATOMIC_INIT(0),
};

struct sony_laptop_keypress {
	struct input_dev *dev;
	int key;
};

/* Correspondance table between sonypi events and input layer events */
static struct {
	int sonypiev;
	int inputev;
} sony_laptop_inputkeys[] = {
	{ SONYPI_EVENT_CAPTURE_PRESSED,	 	KEY_CAMERA },
	{ SONYPI_EVENT_FNKEY_ONLY, 		KEY_FN },
	{ SONYPI_EVENT_FNKEY_ESC, 		KEY_FN_ESC },
	{ SONYPI_EVENT_FNKEY_F1, 		KEY_FN_F1 },
	{ SONYPI_EVENT_FNKEY_F2, 		KEY_FN_F2 },
	{ SONYPI_EVENT_FNKEY_F3, 		KEY_FN_F3 },
	{ SONYPI_EVENT_FNKEY_F4, 		KEY_FN_F4 },
	{ SONYPI_EVENT_FNKEY_F5, 		KEY_FN_F5 },
	{ SONYPI_EVENT_FNKEY_F6, 		KEY_FN_F6 },
	{ SONYPI_EVENT_FNKEY_F7, 		KEY_FN_F7 },
	{ SONYPI_EVENT_FNKEY_F8, 		KEY_FN_F8 },
	{ SONYPI_EVENT_FNKEY_F9,		KEY_FN_F9 },
	{ SONYPI_EVENT_FNKEY_F10,		KEY_FN_F10 },
	{ SONYPI_EVENT_FNKEY_F11, 		KEY_FN_F11 },
	{ SONYPI_EVENT_FNKEY_F12,		KEY_FN_F12 },
	{ SONYPI_EVENT_FNKEY_1, 		KEY_FN_1 },
	{ SONYPI_EVENT_FNKEY_2, 		KEY_FN_2 },
	{ SONYPI_EVENT_FNKEY_D,			KEY_FN_D },
	{ SONYPI_EVENT_FNKEY_E,			KEY_FN_E },
	{ SONYPI_EVENT_FNKEY_F,			KEY_FN_F },
	{ SONYPI_EVENT_FNKEY_S,			KEY_FN_S },
	{ SONYPI_EVENT_FNKEY_B,			KEY_FN_B },
	{ SONYPI_EVENT_BLUETOOTH_PRESSED, 	KEY_BLUE },
	{ SONYPI_EVENT_BLUETOOTH_ON, 		KEY_BLUE },
	{ SONYPI_EVENT_PKEY_P1, 		KEY_PROG1 },
	{ SONYPI_EVENT_PKEY_P2, 		KEY_PROG2 },
	{ SONYPI_EVENT_PKEY_P3, 		KEY_PROG3 },
	{ SONYPI_EVENT_BACK_PRESSED, 		KEY_BACK },
	{ SONYPI_EVENT_HELP_PRESSED, 		KEY_HELP },
	{ SONYPI_EVENT_ZOOM_PRESSED, 		KEY_ZOOM },
	{ SONYPI_EVENT_THUMBPHRASE_PRESSED, 	BTN_THUMB },
	{ 0, 0 },
};

/* release buttons after a short delay if pressed */
static void do_sony_laptop_release_key(struct work_struct *work)
{
	struct sony_laptop_keypress kp;

	while (kfifo_get(sony_laptop_input.fifo, (unsigned char *)&kp,
			 sizeof(kp)) == sizeof(kp)) {
		msleep(10);
		input_report_key(kp.dev, kp.key, 0);
		input_sync(kp.dev);
	}
}
static DECLARE_WORK(sony_laptop_release_key_work,
		do_sony_laptop_release_key);

/* forward event to the input subsytem */
static void sony_laptop_report_input_event(u8 event)
{
	struct input_dev *jog_dev = sony_laptop_input.jog_dev;
	struct input_dev *key_dev = sony_laptop_input.key_dev;
	struct sony_laptop_keypress kp = { NULL };
	int i;

	if (event == SONYPI_EVENT_FNKEY_RELEASED) {
		/* Nothing, not all VAIOs generate this event */
		return;
	}

	/* report events */
	switch (event) {
	/* jog_dev events */
	case SONYPI_EVENT_JOGDIAL_UP:
	case SONYPI_EVENT_JOGDIAL_UP_PRESSED:
		input_report_rel(jog_dev, REL_WHEEL, 1);
		input_sync(jog_dev);
		return;

	case SONYPI_EVENT_JOGDIAL_DOWN:
	case SONYPI_EVENT_JOGDIAL_DOWN_PRESSED:
		input_report_rel(jog_dev, REL_WHEEL, -1);
		input_sync(jog_dev);
		return;

	/* key_dev events */
	case SONYPI_EVENT_JOGDIAL_PRESSED:
		kp.key = BTN_MIDDLE;
		kp.dev = jog_dev;
		break;

	default:
		for (i = 0; sony_laptop_inputkeys[i].sonypiev; i++)
			if (event == sony_laptop_inputkeys[i].sonypiev) {
				kp.dev = key_dev;
				kp.key = sony_laptop_inputkeys[i].inputev;
				break;
			}
		break;
	}

	if (kp.dev) {
		input_report_key(kp.dev, kp.key, 1);
		input_sync(kp.dev);
		kfifo_put(sony_laptop_input.fifo,
			  (unsigned char *)&kp, sizeof(kp));

		if (!work_pending(&sony_laptop_release_key_work))
			queue_work(sony_laptop_input.wq,
					&sony_laptop_release_key_work);
	} else
		dprintk("unknown input event %.2x\n", event);
}

static int sony_laptop_setup_input(void)
{
	struct input_dev *jog_dev;
	struct input_dev *key_dev;
	int i;
	int error;

	/* don't run again if already initialized */
	if (atomic_add_return(1, &sony_laptop_input.users) > 1)
		return 0;

	/* kfifo */
	spin_lock_init(&sony_laptop_input.fifo_lock);
	sony_laptop_input.fifo =
		kfifo_alloc(SONY_LAPTOP_BUF_SIZE, GFP_KERNEL,
			    &sony_laptop_input.fifo_lock);
	if (IS_ERR(sony_laptop_input.fifo)) {
		printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n");
		error = PTR_ERR(sony_laptop_input.fifo);
		goto err_dec_users;
	}

	/* init workqueue */
	sony_laptop_input.wq = create_singlethread_workqueue("sony-laptop");
	if (!sony_laptop_input.wq) {
		printk(KERN_ERR DRV_PFX
				"Unabe to create workqueue.\n");
		error = -ENXIO;
		goto err_free_kfifo;
	}

	/* input keys */
	key_dev = input_allocate_device();
	if (!key_dev) {
		error = -ENOMEM;
		goto err_destroy_wq;
	}

	key_dev->name = "Sony Vaio Keys";
	key_dev->id.bustype = BUS_ISA;
	key_dev->id.vendor = PCI_VENDOR_ID_SONY;

	/* Initialize the Input Drivers: special keys */
	key_dev->evbit[0] = BIT(EV_KEY);
	for (i = 0; sony_laptop_inputkeys[i].sonypiev; i++)
		if (sony_laptop_inputkeys[i].inputev)
			set_bit(sony_laptop_inputkeys[i].inputev,
					key_dev->keybit);

	error = input_register_device(key_dev);
	if (error)
		goto err_free_keydev;

	sony_laptop_input.key_dev = key_dev;

	/* jogdial */
	jog_dev = input_allocate_device();
	if (!jog_dev) {
		error = -ENOMEM;
		goto err_unregister_keydev;
	}

	jog_dev->name = "Sony Vaio Jogdial";
	jog_dev->id.bustype = BUS_ISA;
	jog_dev->id.vendor = PCI_VENDOR_ID_SONY;

	jog_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
	jog_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_MIDDLE);
	jog_dev->relbit[0] = BIT(REL_WHEEL);

	error = input_register_device(jog_dev);
	if (error)
		goto err_free_jogdev;

	sony_laptop_input.jog_dev = jog_dev;

	return 0;

err_free_jogdev:
	input_free_device(jog_dev);

err_unregister_keydev:
	input_unregister_device(key_dev);
	/* to avoid kref underflow below at input_free_device */
	key_dev = NULL;

err_free_keydev:
	input_free_device(key_dev);

err_destroy_wq:
	destroy_workqueue(sony_laptop_input.wq);

err_free_kfifo:
	kfifo_free(sony_laptop_input.fifo);

err_dec_users:
	atomic_dec(&sony_laptop_input.users);
	return error;
}

static void sony_laptop_remove_input(void)
{
	/* cleanup only after the last user has gone */
	if (!atomic_dec_and_test(&sony_laptop_input.users))
		return;

	/* flush workqueue first */
	flush_workqueue(sony_laptop_input.wq);

	/* destroy input devs */
	input_unregister_device(sony_laptop_input.key_dev);
	sony_laptop_input.key_dev = NULL;

	if (sony_laptop_input.jog_dev) {
		input_unregister_device(sony_laptop_input.jog_dev);
		sony_laptop_input.jog_dev = NULL;
	}

	destroy_workqueue(sony_laptop_input.wq);
	kfifo_free(sony_laptop_input.fifo);
}

/*********** Platform Device ***********/

static atomic_t sony_pf_users = ATOMIC_INIT(0);
@@ -428,6 +676,7 @@ static struct backlight_ops sony_backlight_ops = {
static void sony_acpi_notify(acpi_handle handle, u32 event, void *data)
{
	dprintk("sony_acpi_notify, event: %d\n", event);
	sony_laptop_report_input_event(event);
	acpi_bus_generate_event(sony_nc_acpi_device, 1, event);
}

@@ -490,13 +739,21 @@ static int sony_nc_add(struct acpi_device *device)
		}
	}

	/* setup input devices and helper fifo */
	result = sony_laptop_setup_input();
	if (result) {
		printk(KERN_ERR DRV_PFX
				"Unabe to create input devices.\n");
		goto outwalk;
	}

	status = acpi_install_notify_handler(sony_nc_acpi_handle,
					     ACPI_DEVICE_NOTIFY,
					     sony_acpi_notify, NULL);
	if (ACPI_FAILURE(status)) {
		printk(LOG_PFX "unable to install notify handler\n");
		result = -ENODEV;
		goto outwalk;
		goto outinput;
	}

	if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT", &handle))) {
@@ -568,6 +825,7 @@ static int sony_nc_add(struct acpi_device *device)
		device_remove_file(&sony_pf_device->dev, &item->devattr);
	}
	sony_pf_remove();

      outbacklight:
	if (sony_backlight_device)
		backlight_device_unregister(sony_backlight_device);
@@ -577,6 +835,10 @@ static int sony_nc_add(struct acpi_device *device)
					    sony_acpi_notify);
	if (ACPI_FAILURE(status))
		printk(LOG_PFX "unable to remove notify handler\n");

      outinput:
	sony_laptop_remove_input();

      outwalk:
	return result;
}
@@ -602,6 +864,7 @@ static int sony_nc_remove(struct acpi_device *device, int type)
	}

	sony_pf_remove();
	sony_laptop_remove_input();

	printk(KERN_INFO SONY_NC_DRIVER_NAME " successfully removed\n");

@@ -626,13 +889,8 @@ static struct acpi_driver sony_nc_driver = {
#define SONYPI_DEVICE_TYPE2	0x00000002
#define SONYPI_DEVICE_TYPE3	0x00000004

#define SONY_EC_JOGB		0x82
#define SONY_EC_JOGB_MASK	0x02

#define SONY_PIC_EV_MASK	0xff

#define SONYPI_BUF_SIZE	128

struct sony_pic_ioport {
	struct acpi_resource_io	io;
	struct list_head	list;
@@ -650,12 +908,6 @@ struct sony_pic_dev {
	struct sony_pic_ioport	*cur_ioport;
	struct list_head	interrupts;
	struct list_head	ioports;

	struct input_dev	*input_jog_dev;
	struct input_dev	*input_key_dev;
	struct kfifo		*input_fifo;
	spinlock_t		input_fifo_lock;
	struct workqueue_struct	*sony_pic_wq;
};

static struct sony_pic_dev spic_dev = {
@@ -929,250 +1181,9 @@ static u8 sony_pic_call2(u8 dev, u8 fn)
	return v1;
}

/*****************
 *
 * INPUT Device
 *
 *****************/
struct sony_pic_keypress {
	struct input_dev *dev;
	int key;
};

/* Correspondance table between sonypi events and input layer events */
static struct {
	int sonypiev;
	int inputev;
} sony_pic_inputkeys[] = {
	{ SONYPI_EVENT_CAPTURE_PRESSED,	 	KEY_CAMERA },
	{ SONYPI_EVENT_FNKEY_ONLY, 		KEY_FN },
	{ SONYPI_EVENT_FNKEY_ESC, 		KEY_FN_ESC },
	{ SONYPI_EVENT_FNKEY_F1, 		KEY_FN_F1 },
	{ SONYPI_EVENT_FNKEY_F2, 		KEY_FN_F2 },
	{ SONYPI_EVENT_FNKEY_F3, 		KEY_FN_F3 },
	{ SONYPI_EVENT_FNKEY_F4, 		KEY_FN_F4 },
	{ SONYPI_EVENT_FNKEY_F5, 		KEY_FN_F5 },
	{ SONYPI_EVENT_FNKEY_F6, 		KEY_FN_F6 },
	{ SONYPI_EVENT_FNKEY_F7, 		KEY_FN_F7 },
	{ SONYPI_EVENT_FNKEY_F8, 		KEY_FN_F8 },
	{ SONYPI_EVENT_FNKEY_F9,		KEY_FN_F9 },
	{ SONYPI_EVENT_FNKEY_F10,		KEY_FN_F10 },
	{ SONYPI_EVENT_FNKEY_F11, 		KEY_FN_F11 },
	{ SONYPI_EVENT_FNKEY_F12,		KEY_FN_F12 },
	{ SONYPI_EVENT_FNKEY_1, 		KEY_FN_1 },
	{ SONYPI_EVENT_FNKEY_2, 		KEY_FN_2 },
	{ SONYPI_EVENT_FNKEY_D,			KEY_FN_D },
	{ SONYPI_EVENT_FNKEY_E,			KEY_FN_E },
	{ SONYPI_EVENT_FNKEY_F,			KEY_FN_F },
	{ SONYPI_EVENT_FNKEY_S,			KEY_FN_S },
	{ SONYPI_EVENT_FNKEY_B,			KEY_FN_B },
	{ SONYPI_EVENT_BLUETOOTH_PRESSED, 	KEY_BLUE },
	{ SONYPI_EVENT_BLUETOOTH_ON, 		KEY_BLUE },
	{ SONYPI_EVENT_PKEY_P1, 		KEY_PROG1 },
	{ SONYPI_EVENT_PKEY_P2, 		KEY_PROG2 },
	{ SONYPI_EVENT_PKEY_P3, 		KEY_PROG3 },
	{ SONYPI_EVENT_BACK_PRESSED, 		KEY_BACK },
	{ SONYPI_EVENT_HELP_PRESSED, 		KEY_HELP },
	{ SONYPI_EVENT_ZOOM_PRESSED, 		KEY_ZOOM },
	{ SONYPI_EVENT_THUMBPHRASE_PRESSED, 	BTN_THUMB },
	{ 0, 0 },
};

/* release buttons after a short delay if pressed */
static void do_sony_pic_release_key(struct work_struct *work)
{
	struct sony_pic_keypress kp;

	while (kfifo_get(spic_dev.input_fifo, (unsigned char *)&kp,
			 sizeof(kp)) == sizeof(kp)) {
		msleep(10);
		input_report_key(kp.dev, kp.key, 0);
		input_sync(kp.dev);
	}
}
static DECLARE_WORK(sony_pic_release_key_work,
		do_sony_pic_release_key);

/* forward event to the input subsytem */
static void sony_pic_report_input_event(u8 event)
{
	struct input_dev *jog_dev = spic_dev.input_jog_dev;
	struct input_dev *key_dev = spic_dev.input_key_dev;
	struct sony_pic_keypress kp = { NULL };
	int i;

	if (event == SONYPI_EVENT_FNKEY_RELEASED) {
		/* Nothing, not all VAIOs generate this event */
		return;
	}

	/* report jog_dev events */
	if (jog_dev) {
		switch (event) {
		case SONYPI_EVENT_JOGDIAL_UP:
		case SONYPI_EVENT_JOGDIAL_UP_PRESSED:
			input_report_rel(jog_dev, REL_WHEEL, 1);
			input_sync(jog_dev);
			return;

		case SONYPI_EVENT_JOGDIAL_DOWN:
		case SONYPI_EVENT_JOGDIAL_DOWN_PRESSED:
			input_report_rel(jog_dev, REL_WHEEL, -1);
			input_sync(jog_dev);
			return;

		default:
			break;
		}
	}

	switch (event) {
	case SONYPI_EVENT_JOGDIAL_PRESSED:
		kp.key = BTN_MIDDLE;
		kp.dev = jog_dev;
		break;

	default:
		for (i = 0; sony_pic_inputkeys[i].sonypiev; i++)
			if (event == sony_pic_inputkeys[i].sonypiev) {
				kp.dev = key_dev;
				kp.key = sony_pic_inputkeys[i].inputev;
				break;
			}
		break;
	}

	if (kp.dev) {
		input_report_key(kp.dev, kp.key, 1);
		input_sync(kp.dev);
		kfifo_put(spic_dev.input_fifo,
			  (unsigned char *)&kp, sizeof(kp));

		if (!work_pending(&sony_pic_release_key_work))
			queue_work(spic_dev.sony_pic_wq,
					&sony_pic_release_key_work);
	}
}

static int sony_pic_setup_input(void)
{
	struct input_dev *jog_dev;
	struct input_dev *key_dev;
	int i;
	int error;
	u8 jog_present = 0;

	/* kfifo */
	spin_lock_init(&spic_dev.input_fifo_lock);
	spic_dev.input_fifo =
		kfifo_alloc(SONYPI_BUF_SIZE, GFP_KERNEL,
			    &spic_dev.input_fifo_lock);
	if (IS_ERR(spic_dev.input_fifo)) {
		printk(KERN_ERR "sonypi: kfifo_alloc failed\n");
		return PTR_ERR(spic_dev.input_fifo);
	}

	/* init workqueue */
	spic_dev.sony_pic_wq = create_singlethread_workqueue("sony-pic");
	if (!spic_dev.sony_pic_wq) {
		printk(KERN_ERR DRV_PFX
				"Unabe to create workqueue.\n");
		error = -ENXIO;
		goto err_free_kfifo;
	}

	/* input keys */
	key_dev = input_allocate_device();
	if (!key_dev) {
		error = -ENOMEM;
		goto err_destroy_wq;
	}

	key_dev->name = "Sony Vaio Keys";
	key_dev->id.bustype = BUS_ISA;
	key_dev->id.vendor = PCI_VENDOR_ID_SONY;

	/* Initialize the Input Drivers: special keys */
	key_dev->evbit[0] = BIT(EV_KEY);
	for (i = 0; sony_pic_inputkeys[i].sonypiev; i++)
		if (sony_pic_inputkeys[i].inputev)
			set_bit(sony_pic_inputkeys[i].inputev, key_dev->keybit);

	error = input_register_device(key_dev);
	if (error)
		goto err_free_keydev;

	spic_dev.input_key_dev = key_dev;

	/* jogdial - really reliable ? */
	ec_read(SONY_EC_JOGB, &jog_present);
	if (jog_present & SONY_EC_JOGB_MASK || force_jog) {
		jog_dev = input_allocate_device();
		if (!jog_dev) {
			error = -ENOMEM;
			goto err_unregister_keydev;
		}

		jog_dev->name = "Sony Vaio Jogdial";
		jog_dev->id.bustype = BUS_ISA;
		jog_dev->id.vendor = PCI_VENDOR_ID_SONY;

		jog_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
		jog_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_MIDDLE);
		jog_dev->relbit[0] = BIT(REL_WHEEL);

		error = input_register_device(jog_dev);
		if (error)
			goto err_free_jogdev;

		spic_dev.input_jog_dev = jog_dev;
	}

	return 0;

err_free_jogdev:
	input_free_device(jog_dev);

err_unregister_keydev:
	input_unregister_device(key_dev);
	/* to avoid kref underflow below at input_free_device */
	key_dev = NULL;

err_free_keydev:
	input_free_device(key_dev);

err_destroy_wq:
	destroy_workqueue(spic_dev.sony_pic_wq);

err_free_kfifo:
	kfifo_free(spic_dev.input_fifo);

	return error;
}

static void sony_pic_remove_input(void)
{
	/* flush workqueue first */
	flush_workqueue(spic_dev.sony_pic_wq);

	/* destroy input devs */
	input_unregister_device(spic_dev.input_key_dev);
	spic_dev.input_key_dev = NULL;

	if (spic_dev.input_jog_dev) {
		input_unregister_device(spic_dev.input_jog_dev);
		spic_dev.input_jog_dev = NULL;
	}

	destroy_workqueue(spic_dev.sony_pic_wq);
	kfifo_free(spic_dev.input_fifo);
}

/********************
 *
/*
 * ACPI callbacks
 *
 ********************/
 */
static acpi_status
sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
{
@@ -1409,7 +1420,7 @@ static irqreturn_t sony_pic_irq(int irq, void *dev_id)
	return IRQ_HANDLED;

found:
	sony_pic_report_input_event(device_event);
	sony_laptop_report_input_event(device_event);
	acpi_bus_generate_event(spic_dev.acpi_dev, 1, device_event);

	return IRQ_HANDLED;
@@ -1434,7 +1445,7 @@ static int sony_pic_remove(struct acpi_device *device, int type)
	release_region(spic_dev.cur_ioport->io.minimum,
			spic_dev.cur_ioport->io.address_length);

	sony_pic_remove_input();
	sony_laptop_remove_input();

	list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {
		list_del(&io->list);
@@ -1474,7 +1485,7 @@ static int sony_pic_add(struct acpi_device *device)
	}

	/* setup input devices and helper fifo */
	result = sony_pic_setup_input();
	result = sony_laptop_setup_input();
	if (result) {
		printk(KERN_ERR DRV_PFX
				"Unabe to create input devices.\n");
@@ -1535,7 +1546,7 @@ static int sony_pic_add(struct acpi_device *device)
			spic_dev.cur_ioport->io.address_length);

err_remove_input:
	sony_pic_remove_input();
	sony_laptop_remove_input();

err_free_resources:
	list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {