linux/drivers/misc/compal-laptop.c
<<
>>
Prefs
   1/*-*-linux-c-*-*/
   2
   3/*
   4  Copyright (C) 2008 Cezary Jackiewicz <cezary.jackiewicz (at) gmail.com>
   5
   6  based on MSI driver
   7
   8  Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
   9
  10  This program is free software; you can redistribute it and/or modify
  11  it under the terms of the GNU General Public License as published by
  12  the Free Software Foundation; either version 2 of the License, or
  13  (at your option) any later version.
  14
  15  This program is distributed in the hope that it will be useful, but
  16  WITHOUT ANY WARRANTY; without even the implied warranty of
  17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18  General Public License for more details.
  19
  20  You should have received a copy of the GNU General Public License
  21  along with this program; if not, write to the Free Software
  22  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  23  02110-1301, USA.
  24 */
  25
  26/*
  27 * comapl-laptop.c - Compal laptop support.
  28 *
  29 * This driver exports a few files in /sys/devices/platform/compal-laptop/:
  30 *
  31 *   wlan - wlan subsystem state: contains 0 or 1 (rw)
  32 *
  33 *   bluetooth - Bluetooth subsystem state: contains 0 or 1 (rw)
  34 *
  35 *   raw - raw value taken from embedded controller register (ro)
  36 *
  37 * In addition to these platform device attributes the driver
  38 * registers itself in the Linux backlight control subsystem and is
  39 * available to userspace under /sys/class/backlight/compal-laptop/.
  40 *
  41 * This driver might work on other laptops produced by Compal. If you
  42 * want to try it you can pass force=1 as argument to the module which
  43 * will force it to load even when the DMI data doesn't identify the
  44 * laptop as FL9x.
  45 */
  46
  47#include <linux/module.h>
  48#include <linux/kernel.h>
  49#include <linux/init.h>
  50#include <linux/acpi.h>
  51#include <linux/dmi.h>
  52#include <linux/backlight.h>
  53#include <linux/platform_device.h>
  54#include <linux/autoconf.h>
  55
  56#define COMPAL_DRIVER_VERSION "0.2.6"
  57
  58#define COMPAL_LCD_LEVEL_MAX 8
  59
  60#define COMPAL_EC_COMMAND_WIRELESS 0xBB
  61#define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9
  62
  63#define KILLSWITCH_MASK 0x10
  64#define WLAN_MASK       0x01
  65#define BT_MASK         0x02
  66
  67static int force;
  68module_param(force, bool, 0);
  69MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
  70
  71/* Hardware access */
  72
  73static int set_lcd_level(int level)
  74{
  75        if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX)
  76                return -EINVAL;
  77
  78        ec_write(COMPAL_EC_COMMAND_LCD_LEVEL, level);
  79
  80        return 0;
  81}
  82
  83static int get_lcd_level(void)
  84{
  85        u8 result;
  86
  87        ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result);
  88
  89        return (int) result;
  90}
  91
  92static int set_wlan_state(int state)
  93{
  94        u8 result, value;
  95
  96        ec_read(COMPAL_EC_COMMAND_WIRELESS, &result);
  97
  98        if ((result & KILLSWITCH_MASK) == 0)
  99                return -EINVAL;
 100        else {
 101                if (state)
 102                        value = (u8) (result | WLAN_MASK);
 103                else
 104                        value = (u8) (result & ~WLAN_MASK);
 105                ec_write(COMPAL_EC_COMMAND_WIRELESS, value);
 106        }
 107
 108        return 0;
 109}
 110
 111static int set_bluetooth_state(int state)
 112{
 113        u8 result, value;
 114
 115        ec_read(COMPAL_EC_COMMAND_WIRELESS, &result);
 116
 117        if ((result & KILLSWITCH_MASK) == 0)
 118                return -EINVAL;
 119        else {
 120                if (state)
 121                        value = (u8) (result | BT_MASK);
 122                else
 123                        value = (u8) (result & ~BT_MASK);
 124                ec_write(COMPAL_EC_COMMAND_WIRELESS, value);
 125        }
 126
 127        return 0;
 128}
 129
 130static int get_wireless_state(int *wlan, int *bluetooth)
 131{
 132        u8 result;
 133
 134        ec_read(COMPAL_EC_COMMAND_WIRELESS, &result);
 135
 136        if (wlan) {
 137                if ((result & KILLSWITCH_MASK) == 0)
 138                        *wlan = 0;
 139                else
 140                        *wlan = result & WLAN_MASK;
 141        }
 142
 143        if (bluetooth) {
 144                if ((result & KILLSWITCH_MASK) == 0)
 145                        *bluetooth = 0;
 146                else
 147                        *bluetooth = (result & BT_MASK) >> 1;
 148        }
 149
 150        return 0;
 151}
 152
 153/* Backlight device stuff */
 154
 155static int bl_get_brightness(struct backlight_device *b)
 156{
 157        return get_lcd_level();
 158}
 159
 160
 161static int bl_update_status(struct backlight_device *b)
 162{
 163        return set_lcd_level(b->props.brightness);
 164}
 165
 166static struct backlight_ops compalbl_ops = {
 167        .get_brightness = bl_get_brightness,
 168        .update_status  = bl_update_status,
 169};
 170
 171static struct backlight_device *compalbl_device;
 172
 173/* Platform device */
 174
 175static ssize_t show_wlan(struct device *dev,
 176        struct device_attribute *attr, char *buf)
 177{
 178        int ret, enabled;
 179
 180        ret = get_wireless_state(&enabled, NULL);
 181        if (ret < 0)
 182                return ret;
 183
 184        return sprintf(buf, "%i\n", enabled);
 185}
 186
 187static ssize_t show_raw(struct device *dev,
 188        struct device_attribute *attr, char *buf)
 189{
 190        u8 result;
 191
 192        ec_read(COMPAL_EC_COMMAND_WIRELESS, &result);
 193
 194        return sprintf(buf, "%i\n", result);
 195}
 196
 197static ssize_t show_bluetooth(struct device *dev,
 198        struct device_attribute *attr, char *buf)
 199{
 200        int ret, enabled;
 201
 202        ret = get_wireless_state(NULL, &enabled);
 203        if (ret < 0)
 204                return ret;
 205
 206        return sprintf(buf, "%i\n", enabled);
 207}
 208
 209static ssize_t store_wlan_state(struct device *dev,
 210        struct device_attribute *attr, const char *buf, size_t count)
 211{
 212        int state, ret;
 213
 214        if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1))
 215                return -EINVAL;
 216
 217        ret = set_wlan_state(state);
 218        if (ret < 0)
 219                return ret;
 220
 221        return count;
 222}
 223
 224static ssize_t store_bluetooth_state(struct device *dev,
 225        struct device_attribute *attr, const char *buf, size_t count)
 226{
 227        int state, ret;
 228
 229        if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1))
 230                return -EINVAL;
 231
 232        ret = set_bluetooth_state(state);
 233        if (ret < 0)
 234                return ret;
 235
 236        return count;
 237}
 238
 239static DEVICE_ATTR(bluetooth, 0644, show_bluetooth, store_bluetooth_state);
 240static DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan_state);
 241static DEVICE_ATTR(raw, 0444, show_raw, NULL);
 242
 243static struct attribute *compal_attributes[] = {
 244        &dev_attr_bluetooth.attr,
 245        &dev_attr_wlan.attr,
 246        &dev_attr_raw.attr,
 247        NULL
 248};
 249
 250static struct attribute_group compal_attribute_group = {
 251        .attrs = compal_attributes
 252};
 253
 254static struct platform_driver compal_driver = {
 255        .driver = {
 256                .name = "compal-laptop",
 257                .owner = THIS_MODULE,
 258        }
 259};
 260
 261static struct platform_device *compal_device;
 262
 263/* Initialization */
 264
 265static int dmi_check_cb(const struct dmi_system_id *id)
 266{
 267        printk(KERN_INFO "compal-laptop: Identified laptop model '%s'.\n",
 268                id->ident);
 269
 270        return 0;
 271}
 272
 273static struct dmi_system_id __initdata compal_dmi_table[] = {
 274        {
 275                .ident = "FL90/IFL90",
 276                .matches = {
 277                        DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
 278                        DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
 279                },
 280                .callback = dmi_check_cb
 281        },
 282        {
 283                .ident = "FL90/IFL90",
 284                .matches = {
 285                        DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
 286                        DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
 287                },
 288                .callback = dmi_check_cb
 289        },
 290        {
 291                .ident = "FL91/IFL91",
 292                .matches = {
 293                        DMI_MATCH(DMI_BOARD_NAME, "IFL91"),
 294                        DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
 295                },
 296                .callback = dmi_check_cb
 297        },
 298        {
 299                .ident = "FL92/JFL92",
 300                .matches = {
 301                        DMI_MATCH(DMI_BOARD_NAME, "JFL92"),
 302                        DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
 303                },
 304                .callback = dmi_check_cb
 305        },
 306        {
 307                .ident = "FT00/IFT00",
 308                .matches = {
 309                        DMI_MATCH(DMI_BOARD_NAME, "IFT00"),
 310                        DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
 311                },
 312                .callback = dmi_check_cb
 313        },
 314        { }
 315};
 316
 317static int __init compal_init(void)
 318{
 319        int ret;
 320
 321        if (acpi_disabled)
 322                return -ENODEV;
 323
 324        if (!force && !dmi_check_system(compal_dmi_table))
 325                return -ENODEV;
 326
 327        /* Register backlight stuff */
 328
 329        if (!acpi_video_backlight_support()) {
 330                compalbl_device = backlight_device_register("compal-laptop", NULL, NULL,
 331                                                            &compalbl_ops);
 332                if (IS_ERR(compalbl_device))
 333                        return PTR_ERR(compalbl_device);
 334
 335                compalbl_device->props.max_brightness = COMPAL_LCD_LEVEL_MAX-1;
 336        }
 337
 338        ret = platform_driver_register(&compal_driver);
 339        if (ret)
 340                goto fail_backlight;
 341
 342        /* Register platform stuff */
 343
 344        compal_device = platform_device_alloc("compal-laptop", -1);
 345        if (!compal_device) {
 346                ret = -ENOMEM;
 347                goto fail_platform_driver;
 348        }
 349
 350        ret = platform_device_add(compal_device);
 351        if (ret)
 352                goto fail_platform_device1;
 353
 354        ret = sysfs_create_group(&compal_device->dev.kobj,
 355                &compal_attribute_group);
 356        if (ret)
 357                goto fail_platform_device2;
 358
 359        printk(KERN_INFO "compal-laptop: driver "COMPAL_DRIVER_VERSION
 360                " successfully loaded.\n");
 361
 362        return 0;
 363
 364fail_platform_device2:
 365
 366        platform_device_del(compal_device);
 367
 368fail_platform_device1:
 369
 370        platform_device_put(compal_device);
 371
 372fail_platform_driver:
 373
 374        platform_driver_unregister(&compal_driver);
 375
 376fail_backlight:
 377
 378        backlight_device_unregister(compalbl_device);
 379
 380        return ret;
 381}
 382
 383static void __exit compal_cleanup(void)
 384{
 385
 386        sysfs_remove_group(&compal_device->dev.kobj, &compal_attribute_group);
 387        platform_device_unregister(compal_device);
 388        platform_driver_unregister(&compal_driver);
 389        backlight_device_unregister(compalbl_device);
 390
 391        printk(KERN_INFO "compal-laptop: driver unloaded.\n");
 392}
 393
 394module_init(compal_init);
 395module_exit(compal_cleanup);
 396
 397MODULE_AUTHOR("Cezary Jackiewicz");
 398MODULE_DESCRIPTION("Compal Laptop Support");
 399MODULE_VERSION(COMPAL_DRIVER_VERSION);
 400MODULE_LICENSE("GPL");
 401
 402MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*");
 403MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*");
 404MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*");
 405MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*");
 406MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*");
 407