diff options
Diffstat (limited to 'drivers/misc/sony-laptop.c')
-rw-r--r-- | drivers/misc/sony-laptop.c | 541 |
1 files changed, 276 insertions, 265 deletions
diff --git a/drivers/misc/sony-laptop.c b/drivers/misc/sony-laptop.c index b797c8cb47d..cf8d7927dc5 100644 --- a/drivers/misc/sony-laptop.c +++ b/drivers/misc/sony-laptop.c @@ -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 @@ err_release_region: 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) { |