diff options
author | Marco Chiappero <marco@absence.it> | 2012-05-19 17:35:54 +0400 |
---|---|---|
committer | Matthew Garrett <mjg@redhat.com> | 2012-05-31 22:33:35 +0400 |
commit | 54535d083f0ae6ee51a43a7a3e17e3ca89774937 (patch) | |
tree | 7b6e9feafe72be44d821d49a62ff083ca59f94e9 | |
parent | bab7084c745bf4d75b760728387f375fd34dc683 (diff) | |
download | linux-54535d083f0ae6ee51a43a7a3e17e3ca89774937.tar.xz |
sony-laptop: support automatic resume on lid open
A few models offer the chance to set whether to resume from S3 and/or S4
when opening the lid.
[malattia@linux.it: create three sysfs files for S3/4/5 rather than
using a single one accepting a bitmask. Support S5 since the DSDT
exports it. Use a struct to hold all the related values, caching of the
current status value rather than re-reading all the time in the sysfs
show function.]
Signed-off-by: Marco Chiappero <marco@absence.it>
Signed-off-by: Mattia Dongili <malattia@linux.it>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
-rw-r--r-- | drivers/platform/x86/sony-laptop.c | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 2b72e476dfff..bc7f40bcd9cc 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -152,6 +152,9 @@ static int sony_nc_thermal_setup(struct platform_device *pd); static void sony_nc_thermal_cleanup(struct platform_device *pd); static void sony_nc_thermal_resume(void); +static int sony_nc_lid_resume_setup(struct platform_device *pd); +static void sony_nc_lid_resume_cleanup(struct platform_device *pd); + enum sony_nc_rfkill { SONY_WIFI, SONY_BLUETOOTH, @@ -1286,6 +1289,12 @@ static void sony_nc_function_setup(struct acpi_device *device, pr_err("couldn't set up battery care function (%d)\n", result); break; + case 0x0119: + result = sony_nc_lid_resume_setup(pf_device); + if (result) + pr_err("couldn't set up lid resume function (%d)\n", + result); + break; case 0x0122: result = sony_nc_thermal_setup(pf_device); if (result) @@ -1333,6 +1342,9 @@ static void sony_nc_function_cleanup(struct platform_device *pd) case 0x013f: sony_nc_battery_care_cleanup(pd); break; + case 0x0119: + sony_nc_lid_resume_cleanup(pd); + break; case 0x0122: sony_nc_thermal_cleanup(pd); break; @@ -2106,6 +2118,134 @@ static void sony_nc_thermal_resume(void) sony_nc_thermal_mode_set(th_handle->mode); } +/* resume on LID open */ +struct snc_lid_resume_control { + struct device_attribute attrs[3]; + unsigned int status; +}; +static struct snc_lid_resume_control *lid_ctl; + +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 long value; + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + /* the value we have to write to SNC is a bitmask: + * +--------------+ + * | S3 | S4 | S5 | + * +--------------+ + * 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; + + 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)) + return -EIO; + + lid_ctl->status = value; + + return count; +} + +static ssize_t sony_nc_lid_resume_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int pos; + + 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); +} + +static int sony_nc_lid_resume_setup(struct platform_device *pd) +{ + unsigned int result; + int i; + + if (sony_call_snc_handle(0x0119, 0x0000, &result)) + return -EIO; + + lid_ctl = kzalloc(sizeof(struct snc_lid_resume_control), GFP_KERNEL); + if (!lid_ctl) + return -ENOMEM; + + lid_ctl->status = result & 0x7; + + 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++) { + result = device_create_file(&pd->dev, &lid_ctl->attrs[i]); + if (result) + goto liderror; + } + + return 0; + +liderror: + for (; i > 0; i--) + device_remove_file(&pd->dev, &lid_ctl->attrs[i]); + + kfree(lid_ctl); + lid_ctl = NULL; + + return result; +} + +static void sony_nc_lid_resume_cleanup(struct platform_device *pd) +{ + int i; + + if (lid_ctl) { + for (i = 0; i < 3; i++) + device_remove_file(&pd->dev, &lid_ctl->attrs[i]); + + kfree(lid_ctl); + lid_ctl = NULL; + } +} + static void sony_nc_backlight_ng_read_limits(int handle, struct sony_backlight_props *props) { |