asus-laptop: control how BLED and WLED should be exposed
Corentin Chary [Thu, 15 Dec 2011 07:27:34 +0000 (08:27 +0100)]
Let the user tells if BLED and WLED should be exposed as led or
rfkill (the old sysfs are still here, but this adds a standard
interface to control the device).

For example on my A6JC, with WAPF=1, I would do:

$ modprobe asus-laptop wled_type=led bluetooth_type=rfkill

There is still no known way to automatically guess what BLED
and WLED methods will control, it's why user information is needed.

A userspace database could do that automatically, and maybe some DMI
matching in the driver.

Signed-off-by: Corentin Chary <corentin.chary@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>

drivers/platform/x86/asus-laptop.c

index db32d03..e416867 100644 (file)
@@ -81,6 +81,19 @@ static uint wapf = 1;
 module_param(wapf, uint, 0444);
 MODULE_PARM_DESC(wapf, "WAPF value");
 
+static char *wled_type = "unknown";
+static char *bled_type = "unknown";
+
+module_param(wled_type, charp, 0444);
+MODULE_PARM_DESC(wlan_status, "Set the wled type on boot "
+                "(unknown, led or rfkill). "
+                "default is unknown");
+
+module_param(bled_type, charp, 0444);
+MODULE_PARM_DESC(bled_type, "Set the bled type on boot "
+                "(unknown, led or rfkill). "
+                "default is unknown");
+
 static int wlan_status = 1;
 static int bluetooth_status = 1;
 static int wimax_status = -1;
@@ -137,6 +150,11 @@ MODULE_PARM_DESC(als_status, "Set the ALS status on boot "
 #define WM_RSTS                0x08    /* internal wimax */
 #define WW_RSTS                0x20    /* internal wwan */
 
+/* WLED and BLED type */
+#define TYPE_UNKNOWN   0
+#define TYPE_LED       1
+#define TYPE_RFKILL    2
+
 /* LED */
 #define METHOD_MLED            "MLED"
 #define METHOD_TLED            "TLED"
@@ -219,7 +237,8 @@ struct asus_led {
  * Same thing for rfkill
  */
 struct asus_rfkill {
-       int control_id;         /* type of control. Maps to PEGA_* values */
+       /* type of control. Maps to PEGA_* values or *_RSTS  */
+       int control_id;
        struct rfkill *rfkill;
        struct asus_laptop *asus;
 };
@@ -240,6 +259,8 @@ struct asus_laptop {
        struct key_entry *keymap;
        struct input_polled_dev *pega_accel_poll;
 
+       struct asus_led wled;
+       struct asus_led bled;
        struct asus_led mled;
        struct asus_led tled;
        struct asus_led rled;
@@ -248,6 +269,8 @@ struct asus_laptop {
        struct asus_led kled;
        struct workqueue_struct *led_workqueue;
 
+       int wled_type;
+       int bled_type;
        int wireless_status;
        bool have_rsts;
        bool is_pega_lucid;
@@ -600,6 +623,10 @@ static enum led_brightness asus_kled_cdev_get(struct led_classdev *led_cdev)
 
 static void asus_led_exit(struct asus_laptop *asus)
 {
+       if (!IS_ERR_OR_NULL(asus->wled.led.dev))
+               led_classdev_unregister(&asus->wled.led);
+       if (!IS_ERR_OR_NULL(asus->bled.led.dev))
+               led_classdev_unregister(&asus->bled.led);
        if (!IS_ERR_OR_NULL(asus->mled.led.dev))
                led_classdev_unregister(&asus->mled.led);
        if (!IS_ERR_OR_NULL(asus->tled.led.dev))
@@ -641,7 +668,7 @@ static int asus_led_register(struct asus_laptop *asus,
 
 static int asus_led_init(struct asus_laptop *asus)
 {
-       int r;
+       int r = 0;
 
        /*
         * The Pegatron Lucid has no physical leds, but all methods are
@@ -660,6 +687,16 @@ static int asus_led_init(struct asus_laptop *asus)
        if (!asus->led_workqueue)
                return -ENOMEM;
 
+       if (asus->wled_type == TYPE_LED)
+               r = asus_led_register(asus, &asus->wled, "asus::wlan",
+                                     METHOD_WLAN);
+       if (r)
+               goto error;
+       if (asus->bled_type == TYPE_LED)
+               r = asus_led_register(asus, &asus->bled, "asus::bluetooth",
+                                     METHOD_BLUETOOTH);
+       if (r)
+               goto error;
        r = asus_led_register(asus, &asus->mled, "asus::mail", METHOD_MLED);
        if (r)
                goto error;
@@ -962,7 +999,7 @@ static ssize_t store_wlan(struct device *dev, struct device_attribute *attr,
        return sysfs_acpi_set(asus, buf, count, METHOD_WLAN);
 }
 
-/*
+/*e
  * Bluetooth
  */
 static int asus_bluetooth_set(struct asus_laptop *asus, int status)
@@ -1245,6 +1282,23 @@ static const struct rfkill_ops asus_gps_rfkill_ops = {
        .set_block = asus_gps_rfkill_set,
 };
 
+static int asus_rfkill_set(void *data, bool blocked)
+{
+       struct asus_rfkill *rfk = data;
+       struct asus_laptop *asus = rfk->asus;
+
+       if (rfk->control_id == WL_RSTS)
+               return asus_wlan_set(asus, !blocked);
+       else if (rfk->control_id == BT_RSTS)
+               return asus_bluetooth_set(asus, !blocked);
+
+       return -EINVAL;
+}
+
+static const struct rfkill_ops asus_rfkill_ops = {
+       .set_block = asus_rfkill_set,
+};
+
 static void asus_rfkill_terminate(struct asus_rfkill *rfk)
 {
        if (!rfk->rfkill)
@@ -1263,30 +1317,64 @@ static void asus_rfkill_exit(struct asus_laptop *asus)
        asus_rfkill_terminate(&asus->gps);
 }
 
-static int asus_rfkill_init(struct asus_laptop *asus)
+static int asus_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk,
+                            const char *name, int control_id, int type,
+                            const struct rfkill_ops *ops)
 {
        int result;
 
-       if (acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) ||
-           acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) ||
-           acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL))
-               return 0;
-
-       asus->gps.rfkill = rfkill_alloc("asus-gps", &asus->platform_device->dev,
-                                       RFKILL_TYPE_GPS,
-                                       &asus_gps_rfkill_ops, asus);
-       if (!asus->gps.rfkill)
+       rfk->control_id = control_id;
+       rfk->asus = asus;
+       rfk->rfkill = rfkill_alloc(name, &asus->platform_device->dev,
+                                  type, ops, rfk);
+       if (!rfk->rfkill)
                return -EINVAL;
 
-       result = rfkill_register(asus->gps.rfkill);
+       result = rfkill_register(rfk->rfkill);
        if (result) {
-               rfkill_destroy(asus->gps.rfkill);
-               asus->gps.rfkill = NULL;
+               rfkill_destroy(rfk->rfkill);
+               rfk->rfkill = NULL;
        }
 
        return result;
 }
 
+static int asus_rfkill_init(struct asus_laptop *asus)
+{
+       int result = 0;
+
+       if (!acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) &&
+           !acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) &&
+           !acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL))
+               result = asus_rfkill_setup(asus, &asus->gps, "asus-gps",
+                                          -1, RFKILL_TYPE_GPS,
+                                          &asus_gps_rfkill_ops);
+       if (result)
+               goto exit;
+
+
+       if (asus->wled_type == TYPE_RFKILL)
+               result = asus_rfkill_setup(asus, &asus->wlan, "asus-wlan",
+                                          WL_RSTS, RFKILL_TYPE_WLAN,
+                                          &asus_rfkill_ops);
+       if (result)
+               goto exit;
+
+       if (asus->bled_type == TYPE_RFKILL)
+               result = asus_rfkill_setup(asus, &asus->bluetooth,
+                                          "asus-bluetooth", BT_RSTS,
+                                          RFKILL_TYPE_BLUETOOTH,
+                                          &asus_rfkill_ops);
+       if (result)
+               goto exit;
+
+exit:
+       if (result)
+               asus_rfkill_exit(asus);
+
+       return result;
+}
+
 static int pega_rfkill_set(void *data, bool blocked)
 {
        struct asus_rfkill *rfk = data;
@@ -1302,22 +1390,8 @@ static const struct rfkill_ops pega_rfkill_ops = {
 static int pega_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk,
                             const char *name, int controlid, int rfkill_type)
 {
-       int result;
-
-       rfk->control_id = controlid;
-       rfk->asus = asus;
-       rfk->rfkill = rfkill_alloc(name, &asus->platform_device->dev,
-                                  rfkill_type, &pega_rfkill_ops, rfk);
-       if (!rfk->rfkill)
-               return -EINVAL;
-
-       result = rfkill_register(rfk->rfkill);
-       if (result) {
-               rfkill_destroy(rfk->rfkill);
-               rfk->rfkill = NULL;
-       }
-
-       return result;
+       return asus_rfkill_setup(asus, rfk, name, controlid, rfkill_type,
+                                &pega_rfkill_ops);
 }
 
 static int pega_rfkill_init(struct asus_laptop *asus)
@@ -1678,7 +1752,16 @@ static int __devinit asus_acpi_init(struct asus_laptop *asus)
        if (result)
                return result;
 
-       /* WLED and BLED are on by default */
+       if (!strcmp(bled_type, "led"))
+               asus->bled_type = TYPE_LED;
+       else if (!strcmp(bled_type, "rfkill"))
+               asus->bled_type = TYPE_RFKILL;
+
+       if (!strcmp(wled_type, "led"))
+               asus->wled_type = TYPE_LED;
+       else if (!strcmp(wled_type, "rfkill"))
+               asus->wled_type = TYPE_RFKILL;
+
        if (bluetooth_status >= 0)
                asus_bluetooth_set(asus, !!bluetooth_status);