diff options
Diffstat (limited to 'drivers/platform/x86/sony-laptop.c')
-rw-r--r-- | drivers/platform/x86/sony-laptop.c | 539 |
1 files changed, 481 insertions, 58 deletions
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 8f8551a63cc0..9c5a07417b2b 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -76,8 +76,6 @@ do { \ pr_warn(fmt, ##__VA_ARGS__); \ } while (0) -#define SONY_LAPTOP_DRIVER_VERSION "0.6" - #define SONY_NC_CLASS "sony-nc" #define SONY_NC_HID "SNY5001" #define SONY_NC_DRIVER_NAME "Sony Notebook Control Driver" @@ -89,7 +87,6 @@ do { \ MODULE_AUTHOR("Stelian Pop, Mattia Dongili"); MODULE_DESCRIPTION("Sony laptop extras driver (SPIC and SNC ACPI device)"); MODULE_LICENSE("GPL"); -MODULE_VERSION(SONY_LAPTOP_DRIVER_VERSION); static int debug; module_param(debug, int, 0); @@ -129,7 +126,8 @@ static int kbd_backlight = -1; module_param(kbd_backlight, int, 0444); MODULE_PARM_DESC(kbd_backlight, "set this to 0 to disable keyboard backlight, " - "1 to enable it (default: no change from current value)"); + "1 to enable it with automatic control and 2 to have it always " + "on (default: no change from current value)"); static int kbd_backlight_timeout = -1; module_param(kbd_backlight_timeout, int, 0444); @@ -152,7 +150,8 @@ static void sony_nc_battery_care_cleanup(struct platform_device *pd); static int sony_nc_thermal_setup(struct platform_device *pd); static void sony_nc_thermal_cleanup(struct platform_device *pd); -static int sony_nc_lid_resume_setup(struct platform_device *pd); +static int sony_nc_lid_resume_setup(struct platform_device *pd, + unsigned int handle); static void sony_nc_lid_resume_cleanup(struct platform_device *pd); static int sony_nc_gfx_switch_setup(struct platform_device *pd, @@ -163,6 +162,21 @@ static int __sony_nc_gfx_switch_status_get(void); static int sony_nc_highspeed_charging_setup(struct platform_device *pd); static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd); +static int sony_nc_lowbatt_setup(struct platform_device *pd); +static void sony_nc_lowbatt_cleanup(struct platform_device *pd); + +static int sony_nc_fanspeed_setup(struct platform_device *pd); +static void sony_nc_fanspeed_cleanup(struct platform_device *pd); + +static int sony_nc_usb_charge_setup(struct platform_device *pd); +static void sony_nc_usb_charge_cleanup(struct platform_device *pd); + +static int sony_nc_panelid_setup(struct platform_device *pd); +static void sony_nc_panelid_cleanup(struct platform_device *pd); + +static int sony_nc_smart_conn_setup(struct platform_device *pd); +static void sony_nc_smart_conn_cleanup(struct platform_device *pd); + static int sony_nc_touchpad_setup(struct platform_device *pd, unsigned int handle); static void sony_nc_touchpad_cleanup(struct platform_device *pd); @@ -1122,6 +1136,8 @@ static struct sony_nc_event sony_100_events[] = { { 0x25, SONYPI_EVENT_ANYBUTTON_RELEASED }, { 0xa6, SONYPI_EVENT_HELP_PRESSED }, { 0x26, SONYPI_EVENT_ANYBUTTON_RELEASED }, + { 0xa8, SONYPI_EVENT_FNKEY_1 }, + { 0x28, SONYPI_EVENT_ANYBUTTON_RELEASED }, { 0, 0 }, }; @@ -1339,7 +1355,8 @@ static void sony_nc_function_setup(struct acpi_device *device, result); break; case 0x0119: - result = sony_nc_lid_resume_setup(pf_device); + case 0x015D: + result = sony_nc_lid_resume_setup(pf_device, handle); if (result) pr_err("couldn't set up lid resume function (%d)\n", result); @@ -1381,6 +1398,36 @@ static void sony_nc_function_setup(struct acpi_device *device, pr_err("couldn't set up keyboard backlight function (%d)\n", result); break; + case 0x0121: + result = sony_nc_lowbatt_setup(pf_device); + if (result) + pr_err("couldn't set up low battery function (%d)\n", + result); + break; + case 0x0149: + result = sony_nc_fanspeed_setup(pf_device); + if (result) + pr_err("couldn't set up fan speed function (%d)\n", + result); + break; + case 0x0155: + result = sony_nc_usb_charge_setup(pf_device); + if (result) + pr_err("couldn't set up USB charge support (%d)\n", + result); + break; + case 0x011D: + result = sony_nc_panelid_setup(pf_device); + if (result) + pr_err("couldn't set up panel ID function (%d)\n", + result); + break; + case 0x0168: + result = sony_nc_smart_conn_setup(pf_device); + if (result) + pr_err("couldn't set up smart connect support (%d)\n", + result); + break; default: continue; } @@ -1420,6 +1467,7 @@ static void sony_nc_function_cleanup(struct platform_device *pd) sony_nc_battery_care_cleanup(pd); break; case 0x0119: + case 0x015D: sony_nc_lid_resume_cleanup(pd); break; case 0x0122: @@ -1444,6 +1492,21 @@ static void sony_nc_function_cleanup(struct platform_device *pd) case 0x0163: sony_nc_kbd_backlight_cleanup(pd, handle); break; + case 0x0121: + sony_nc_lowbatt_cleanup(pd); + break; + case 0x0149: + sony_nc_fanspeed_cleanup(pd); + break; + case 0x0155: + sony_nc_usb_charge_cleanup(pd); + break; + case 0x011D: + sony_nc_panelid_cleanup(pd); + break; + case 0x0168: + sony_nc_smart_conn_cleanup(pd); + break; default: continue; } @@ -1719,7 +1782,7 @@ static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value) { int result; - if (value > 1) + if (value > 2) return -EINVAL; if (sony_call_snc_handle(kbdbl_ctl->handle, @@ -1727,8 +1790,10 @@ static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value) return -EIO; /* Try to turn the light on/off immediately */ - sony_call_snc_handle(kbdbl_ctl->handle, - (value << 0x10) | (kbdbl_ctl->base + 0x100), &result); + if (value != 1) + sony_call_snc_handle(kbdbl_ctl->handle, + (value << 0x0f) | (kbdbl_ctl->base + 0x100), + &result); kbdbl_ctl->mode = value; @@ -2221,9 +2286,14 @@ static void sony_nc_thermal_resume(void) #endif /* resume on LID open */ +#define LID_RESUME_S5 0 +#define LID_RESUME_S4 1 +#define LID_RESUME_S3 2 +#define LID_RESUME_MAX 3 struct snc_lid_resume_control { - struct device_attribute attrs[3]; + struct device_attribute attrs[LID_RESUME_MAX]; unsigned int status; + int handle; }; static struct snc_lid_resume_control *lid_ctl; @@ -2231,8 +2301,9 @@ static ssize_t sony_nc_lid_resume_store(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count) { - unsigned int result, pos; + unsigned int result; unsigned long value; + unsigned int pos = LID_RESUME_S5; if (count > 31) return -EINVAL; @@ -2245,21 +2316,21 @@ static ssize_t sony_nc_lid_resume_store(struct device *dev, * +--------------+ * 2 1 0 */ - if (strcmp(attr->attr.name, "lid_resume_S3") == 0) - pos = 2; - else if (strcmp(attr->attr.name, "lid_resume_S4") == 0) - pos = 1; - else if (strcmp(attr->attr.name, "lid_resume_S5") == 0) - pos = 0; - else - return -EINVAL; + while (pos < LID_RESUME_MAX) { + if (&lid_ctl->attrs[pos].attr == &attr->attr) + break; + pos++; + } + if (pos == LID_RESUME_MAX) + return -EINVAL; if (value) value = lid_ctl->status | (1 << pos); else value = lid_ctl->status & ~(1 << pos); - if (sony_call_snc_handle(0x0119, value << 0x10 | 0x0100, &result)) + if (sony_call_snc_handle(lid_ctl->handle, value << 0x10 | 0x0100, + &result)) return -EIO; lid_ctl->status = value; @@ -2268,29 +2339,27 @@ static ssize_t sony_nc_lid_resume_store(struct device *dev, } static ssize_t sony_nc_lid_resume_show(struct device *dev, - struct device_attribute *attr, char *buffer) + struct device_attribute *attr, + char *buffer) { - unsigned int pos; + unsigned int pos = LID_RESUME_S5; - if (strcmp(attr->attr.name, "lid_resume_S3") == 0) - pos = 2; - else if (strcmp(attr->attr.name, "lid_resume_S4") == 0) - pos = 1; - else if (strcmp(attr->attr.name, "lid_resume_S5") == 0) - pos = 0; - else - return -EINVAL; - - return snprintf(buffer, PAGE_SIZE, "%d\n", - (lid_ctl->status >> pos) & 0x01); + while (pos < LID_RESUME_MAX) { + if (&lid_ctl->attrs[pos].attr == &attr->attr) + return snprintf(buffer, PAGE_SIZE, "%d\n", + (lid_ctl->status >> pos) & 0x01); + pos++; + } + return -EINVAL; } -static int sony_nc_lid_resume_setup(struct platform_device *pd) +static int sony_nc_lid_resume_setup(struct platform_device *pd, + unsigned int handle) { unsigned int result; int i; - if (sony_call_snc_handle(0x0119, 0x0000, &result)) + if (sony_call_snc_handle(handle, 0x0000, &result)) return -EIO; lid_ctl = kzalloc(sizeof(struct snc_lid_resume_control), GFP_KERNEL); @@ -2298,26 +2367,29 @@ static int sony_nc_lid_resume_setup(struct platform_device *pd) return -ENOMEM; lid_ctl->status = result & 0x7; + lid_ctl->handle = handle; sysfs_attr_init(&lid_ctl->attrs[0].attr); - lid_ctl->attrs[0].attr.name = "lid_resume_S3"; - lid_ctl->attrs[0].attr.mode = S_IRUGO | S_IWUSR; - lid_ctl->attrs[0].show = sony_nc_lid_resume_show; - lid_ctl->attrs[0].store = sony_nc_lid_resume_store; - - sysfs_attr_init(&lid_ctl->attrs[1].attr); - lid_ctl->attrs[1].attr.name = "lid_resume_S4"; - lid_ctl->attrs[1].attr.mode = S_IRUGO | S_IWUSR; - lid_ctl->attrs[1].show = sony_nc_lid_resume_show; - lid_ctl->attrs[1].store = sony_nc_lid_resume_store; - - sysfs_attr_init(&lid_ctl->attrs[2].attr); - lid_ctl->attrs[2].attr.name = "lid_resume_S5"; - lid_ctl->attrs[2].attr.mode = S_IRUGO | S_IWUSR; - lid_ctl->attrs[2].show = sony_nc_lid_resume_show; - lid_ctl->attrs[2].store = sony_nc_lid_resume_store; - - for (i = 0; i < 3; i++) { + lid_ctl->attrs[LID_RESUME_S5].attr.name = "lid_resume_S5"; + lid_ctl->attrs[LID_RESUME_S5].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S5].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S5].store = sony_nc_lid_resume_store; + + if (handle == 0x0119) { + sysfs_attr_init(&lid_ctl->attrs[1].attr); + lid_ctl->attrs[LID_RESUME_S4].attr.name = "lid_resume_S4"; + lid_ctl->attrs[LID_RESUME_S4].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S4].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S4].store = sony_nc_lid_resume_store; + + sysfs_attr_init(&lid_ctl->attrs[2].attr); + lid_ctl->attrs[LID_RESUME_S3].attr.name = "lid_resume_S3"; + lid_ctl->attrs[LID_RESUME_S3].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S3].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S3].store = sony_nc_lid_resume_store; + } + for (i = 0; i < LID_RESUME_MAX && + lid_ctl->attrs[LID_RESUME_S3].attr.name; i++) { result = device_create_file(&pd->dev, &lid_ctl->attrs[i]); if (result) goto liderror; @@ -2340,8 +2412,12 @@ static void sony_nc_lid_resume_cleanup(struct platform_device *pd) int i; if (lid_ctl) { - for (i = 0; i < 3; i++) + for (i = 0; i < LID_RESUME_MAX; i++) { + if (!lid_ctl->attrs[i].attr.name) + break; + device_remove_file(&pd->dev, &lid_ctl->attrs[i]); + } kfree(lid_ctl); lid_ctl = NULL; @@ -2524,6 +2600,355 @@ static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd) } } +/* low battery function */ +static struct device_attribute *lowbatt_handle; + +static ssize_t sony_nc_lowbatt_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0121, value << 8, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_lowbatt_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0121, 0x0200, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 1); +} + +static int sony_nc_lowbatt_setup(struct platform_device *pd) +{ + unsigned int result; + + lowbatt_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!lowbatt_handle) + return -ENOMEM; + + sysfs_attr_init(&lowbatt_handle->attr); + lowbatt_handle->attr.name = "lowbatt_hibernate"; + lowbatt_handle->attr.mode = S_IRUGO | S_IWUSR; + lowbatt_handle->show = sony_nc_lowbatt_show; + lowbatt_handle->store = sony_nc_lowbatt_store; + + result = device_create_file(&pd->dev, lowbatt_handle); + if (result) { + kfree(lowbatt_handle); + lowbatt_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_lowbatt_cleanup(struct platform_device *pd) +{ + if (lowbatt_handle) { + device_remove_file(&pd->dev, lowbatt_handle); + kfree(lowbatt_handle); + lowbatt_handle = NULL; + } +} + +/* fan speed function */ +static struct device_attribute *fan_handle, *hsf_handle; + +static ssize_t sony_nc_hsfan_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0149, value << 0x10 | 0x0200, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_hsfan_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0149, 0x0100, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01); +} + +static ssize_t sony_nc_fanspeed_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0149, 0x0300, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0xff); +} + +static int sony_nc_fanspeed_setup(struct platform_device *pd) +{ + unsigned int result; + + fan_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!fan_handle) + return -ENOMEM; + + hsf_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!hsf_handle) { + result = -ENOMEM; + goto out_hsf_handle_alloc; + } + + sysfs_attr_init(&fan_handle->attr); + fan_handle->attr.name = "fanspeed"; + fan_handle->attr.mode = S_IRUGO; + fan_handle->show = sony_nc_fanspeed_show; + fan_handle->store = NULL; + + sysfs_attr_init(&hsf_handle->attr); + hsf_handle->attr.name = "fan_forced"; + hsf_handle->attr.mode = S_IRUGO | S_IWUSR; + hsf_handle->show = sony_nc_hsfan_show; + hsf_handle->store = sony_nc_hsfan_store; + + result = device_create_file(&pd->dev, fan_handle); + if (result) + goto out_fan_handle; + + result = device_create_file(&pd->dev, hsf_handle); + if (result) + goto out_hsf_handle; + + return 0; + +out_hsf_handle: + device_remove_file(&pd->dev, fan_handle); + +out_fan_handle: + kfree(hsf_handle); + hsf_handle = NULL; + +out_hsf_handle_alloc: + kfree(fan_handle); + fan_handle = NULL; + return result; +} + +static void sony_nc_fanspeed_cleanup(struct platform_device *pd) +{ + if (fan_handle) { + device_remove_file(&pd->dev, fan_handle); + kfree(fan_handle); + fan_handle = NULL; + } + if (hsf_handle) { + device_remove_file(&pd->dev, hsf_handle); + kfree(hsf_handle); + hsf_handle = NULL; + } +} + +/* USB charge function */ +static struct device_attribute *uc_handle; + +static ssize_t sony_nc_usb_charge_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0155, value << 0x10 | 0x0100, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_usb_charge_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0155, 0x0000, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01); +} + +static int sony_nc_usb_charge_setup(struct platform_device *pd) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0155, 0x0000, &result) || !(result & 0x01)) { + /* some models advertise the handle but have no implementation + * for it + */ + pr_info("No USB Charge capability found\n"); + return 0; + } + + uc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!uc_handle) + return -ENOMEM; + + sysfs_attr_init(&uc_handle->attr); + uc_handle->attr.name = "usb_charge"; + uc_handle->attr.mode = S_IRUGO | S_IWUSR; + uc_handle->show = sony_nc_usb_charge_show; + uc_handle->store = sony_nc_usb_charge_store; + + result = device_create_file(&pd->dev, uc_handle); + if (result) { + kfree(uc_handle); + uc_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_usb_charge_cleanup(struct platform_device *pd) +{ + if (uc_handle) { + device_remove_file(&pd->dev, uc_handle); + kfree(uc_handle); + uc_handle = NULL; + } +} + +/* Panel ID function */ +static struct device_attribute *panel_handle; + +static ssize_t sony_nc_panelid_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x011D, 0x0000, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result); +} + +static int sony_nc_panelid_setup(struct platform_device *pd) +{ + unsigned int result; + + panel_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!panel_handle) + return -ENOMEM; + + sysfs_attr_init(&panel_handle->attr); + panel_handle->attr.name = "panel_id"; + panel_handle->attr.mode = S_IRUGO; + panel_handle->show = sony_nc_panelid_show; + panel_handle->store = NULL; + + result = device_create_file(&pd->dev, panel_handle); + if (result) { + kfree(panel_handle); + panel_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_panelid_cleanup(struct platform_device *pd) +{ + if (panel_handle) { + device_remove_file(&pd->dev, panel_handle); + kfree(panel_handle); + panel_handle = NULL; + } +} + +/* smart connect function */ +static struct device_attribute *sc_handle; + +static ssize_t sony_nc_smart_conn_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0168, value << 0x10, &result)) + return -EIO; + + return count; +} + +static int sony_nc_smart_conn_setup(struct platform_device *pd) +{ + unsigned int result; + + sc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!sc_handle) + return -ENOMEM; + + sysfs_attr_init(&sc_handle->attr); + sc_handle->attr.name = "smart_connect"; + sc_handle->attr.mode = S_IWUSR; + sc_handle->show = NULL; + sc_handle->store = sony_nc_smart_conn_store; + + result = device_create_file(&pd->dev, sc_handle); + if (result) { + kfree(sc_handle); + sc_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_smart_conn_cleanup(struct platform_device *pd) +{ + if (sc_handle) { + device_remove_file(&pd->dev, sc_handle); + kfree(sc_handle); + sc_handle = NULL; + } +} + /* Touchpad enable/disable */ struct touchpad_control { struct device_attribute attr; @@ -2726,8 +3151,6 @@ static int sony_nc_add(struct acpi_device *device) int result = 0; struct sony_nc_value *item; - pr_info("%s v%s\n", SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); - sony_nc_acpi_device = device; strcpy(acpi_device_class(device), "sony/hotkey"); @@ -2821,6 +3244,7 @@ static int sony_nc_add(struct acpi_device *device) } } + pr_info("SNC setup done.\n"); return 0; out_sysfs: @@ -4259,8 +4683,6 @@ static int sony_pic_add(struct acpi_device *device) struct sony_pic_ioport *io, *tmp_io; struct sony_pic_irq *irq, *tmp_irq; - pr_info("%s v%s\n", SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); - spic_dev.acpi_dev = device; strcpy(acpi_device_class(device), "sony/hotkey"); sony_pic_detect_device_type(&spic_dev); @@ -4360,6 +4782,7 @@ static int sony_pic_add(struct acpi_device *device) if (result) goto err_remove_pf; + pr_info("SPIC setup done.\n"); return 0; err_remove_pf: |