summaryrefslogtreecommitdiff
path: root/drivers/platform
diff options
context:
space:
mode:
authorKenneth Chan <kenneth.t.chan@gmail.com>2020-08-21 21:14:25 +0300
committerHans de Goede <hdegoede@redhat.com>2020-11-10 16:47:06 +0300
commitd5a81d8e864bb1faebeafac0c79b39937701008f (patch)
tree641c21fe0e5281c94953877b0c65416e066c6c60 /drivers/platform
parent19cf70546b24ff3e944f0df323774351e830e203 (diff)
downloadlinux-d5a81d8e864bb1faebeafac0c79b39937701008f.tar.xz
platform/x86: panasonic-laptop: Add support for optical driver power in Y and W series
The physical optical drive switch is present in Y and W series that switches on the drive but fails to turn it off. The idea is to be able to toggle the drive power by software and/or hardware. This patch merges Martin Lucina <mato@kotelna.sk>'s work that took care of the software part. Code is also added for the physical switch to power off the drive. Signed-off-by: Kenneth Chan <kenneth.t.chan@gmail.com> Link: https://lore.kernel.org/r/20200821181433.17653-2-kenneth.t.chan@gmail.com Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/x86/panasonic-laptop.c190
1 files changed, 190 insertions, 0 deletions
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 59e38a1d2830..21cdc2149a10 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -12,6 +12,13 @@
*---------------------------------------------------------------------------
*
* ChangeLog:
+ * Aug.18, 2020 Kenneth Chan <kenneth.t.chan@gmail.com>
+ * -v0.97 add support for cdpower hardware switch
+ * -v0.96 merge Lucina's enhancement
+ * Jan.13, 2009 Martin Lucina <mato@kotelna.sk>
+ * - add support for optical driver power in
+ * Y and W series
+ *
* Sep.23, 2008 Harald Welte <laforge@gnumonks.org>
* -v0.95 rename driver from drivers/acpi/pcc_acpi.c to
* drivers/misc/panasonic-laptop.c
@@ -115,6 +122,7 @@
#include <linux/acpi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
+#include <linux/platform_device.h>
#ifndef ACPI_HOTKEY_COMPONENT
#define ACPI_HOTKEY_COMPONENT 0x10000000
@@ -213,6 +221,7 @@ struct pcc_acpi {
struct acpi_device *device;
struct input_dev *input_dev;
struct backlight_device *backlight;
+ struct platform_device *platform;
};
/* method access functions */
@@ -345,6 +354,98 @@ static const struct backlight_ops pcc_backlight_ops = {
};
+/* returns ACPI_SUCCESS if methods to control optical drive are present */
+
+static acpi_status check_optd_present(void)
+{
+ acpi_status status = AE_OK;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, "\\_SB.STAT", &handle);
+ if (ACPI_FAILURE(status))
+ goto out;
+ status = acpi_get_handle(NULL, "\\_SB.FBAY", &handle);
+ if (ACPI_FAILURE(status))
+ goto out;
+ status = acpi_get_handle(NULL, "\\_SB.CDDI", &handle);
+ if (ACPI_FAILURE(status))
+ goto out;
+
+out:
+ return status;
+}
+
+/* get optical driver power state */
+
+static int get_optd_power_state(void)
+{
+ acpi_status status;
+ unsigned long long state;
+ int result;
+
+ status = acpi_evaluate_integer(NULL, "\\_SB.STAT", NULL, &state);
+ if (ACPI_FAILURE(status)) {
+ pr_err("evaluation error _SB.STAT\n");
+ result = -EIO;
+ goto out;
+ }
+ switch (state) {
+ case 0: /* power off */
+ result = 0;
+ break;
+ case 0x0f: /* power on */
+ result = 1;
+ break;
+ default:
+ result = -EIO;
+ break;
+ }
+
+out:
+ return result;
+}
+
+/* set optical drive power state */
+
+static int set_optd_power_state(int new_state)
+{
+ int result;
+ acpi_status status;
+
+ result = get_optd_power_state();
+ if (result < 0)
+ goto out;
+ if (new_state == result)
+ goto out;
+
+ switch (new_state) {
+ case 0: /* power off */
+ /* Call CDDR instead, since they both call the same method
+ * while CDDI takes 1 arg and we are not quite sure what it is.
+ */
+ status = acpi_evaluate_object(NULL, "\\_SB.CDDR", NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_err("evaluation error _SB.CDDR\n");
+ result = -EIO;
+ }
+ break;
+ case 1: /* power on */
+ status = acpi_evaluate_object(NULL, "\\_SB.FBAY", NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_err("evaluation error _SB.FBAY\n");
+ result = -EIO;
+ }
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+
+out:
+ return result;
+}
+
+
/* sysfs user interface functions */
static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
@@ -411,16 +512,36 @@ static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
return count;
}
+static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", get_optd_power_state());
+}
+
+static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int err, val;
+
+ err = kstrtoint(buf, 10, &val);
+ if (err)
+ return err;
+ set_optd_power_state(val);
+ return count;
+}
+
static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL);
static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL);
static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL);
static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky);
+static DEVICE_ATTR_RW(cdpower);
static struct attribute *pcc_sysfs_entries[] = {
&dev_attr_numbatt.attr,
&dev_attr_lcdtype.attr,
&dev_attr_mute.attr,
&dev_attr_sticky_key.attr,
+ &dev_attr_cdpower.attr,
NULL,
};
@@ -476,6 +597,50 @@ static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
}
}
+static void pcc_optd_notify(acpi_handle handle, u32 event, void *data)
+{
+ if (event != ACPI_NOTIFY_EJECT_REQUEST)
+ return;
+
+ set_optd_power_state(0);
+}
+
+static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node)
+{
+ acpi_status status;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_install_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ pcc_optd_notify, pcc);
+ if (ACPI_FAILURE(status))
+ pr_err("Failed to register notify on %s\n", node);
+ } else
+ return -ENODEV;
+
+ return 0;
+}
+
+static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node)
+{
+ acpi_status status = AE_OK;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_remove_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ pcc_optd_notify);
+ if (ACPI_FAILURE(status))
+ pr_err("Error removing optd notify handler %s\n",
+ node);
+ }
+}
+
static int acpi_pcc_init_input(struct pcc_acpi *pcc)
{
struct input_dev *input_dev;
@@ -606,8 +771,27 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
if (result)
goto out_backlight;
+ /* optical drive initialization */
+ if (ACPI_SUCCESS(check_optd_present())) {
+ pcc->platform = platform_device_register_simple("panasonic",
+ -1, NULL, 0);
+ if (IS_ERR(pcc->platform)) {
+ result = PTR_ERR(pcc->platform);
+ goto out_backlight;
+ }
+ result = device_create_file(&pcc->platform->dev,
+ &dev_attr_cdpower);
+ pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
+ if (result)
+ goto out_platform;
+ } else {
+ pcc->platform = NULL;
+ }
+
return 0;
+out_platform:
+ platform_device_unregister(pcc->platform);
out_backlight:
backlight_device_unregister(pcc->backlight);
out_input:
@@ -627,6 +811,12 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device)
if (!device || !pcc)
return -EINVAL;
+ if (pcc->platform) {
+ device_remove_file(&pcc->platform->dev, &dev_attr_cdpower);
+ platform_device_unregister(pcc->platform);
+ }
+ pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
+
sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
backlight_device_unregister(pcc->backlight);