/*-*-linux-c-*-*/
/*
Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
/*
* msi-laptop.c - MSI S270 laptop support. This laptop is sold under
* various brands, including "Cytron/TCM/Medion/Tchibo MD96100".
*
* Driver also supports S271, S420 models.
*
* This driver exports a few files in /sys/devices/platform/msi-laptop-pf/:
*
* lcd_level - Screen brightness: contains a single integer in the
* range 0..8. (rw)
*
* auto_brightness - Enable automatic brightness control: contains
* either 0 or 1. If set to 1 the hardware adjusts the screen
* brightness automatically when the power cord is
* plugged/unplugged. (rw)
*
* wlan - WLAN subsystem enabled: contains either 0 or 1. (ro)
*
* bluetooth - Bluetooth subsystem enabled: contains either 0 or 1
* Please note that this file is constantly 0 if no Bluetooth
* hardware is available. (ro)
*
* In addition to these platform device attributes the driver
* registers itself in the Linux backlight control subsystem and is
* available to userspace under /sys/class/backlight/msi-laptop-bl/.
*
* This driver might work on other laptops produced by MSI. If you
* want to try it you can pass force=1 as argument to the module which
* will force it to load even when the DMI data doesn't identify the
* laptop as MSI S270. YMMV.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/backlight.h>
#include <linux/platform_device.h>
#include <linux/rfkill.h>
#include <linux/i8042.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#define MSI_DRIVER_VERSION "0.5"
#define MSI_LCD_LEVEL_MAX 9
#define MSI_EC_COMMAND_WIRELESS 0x10
#define MSI_EC_COMMAND_LCD_LEVEL 0x11
#define MSI_STANDARD_EC_COMMAND_ADDRESS 0x2e
#define MSI_STANDARD_EC_BLUETOOTH_MASK (1 << 0)
#define MSI_STANDARD_EC_WEBCAM_MASK (1 << 1)
#define MSI_STANDARD_EC_WLAN_MASK (1 << 3)
#define MSI_STANDARD_EC_3G_MASK (1 << 4)
/* For set SCM load flag to disable BIOS fn key */
#define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d
#define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0)
#define MSI_STANDARD_EC_FUNCTIONS_ADDRESS 0xe4
/* Power LED is orange - Turbo mode */
#define MSI_STANDARD_EC_TURBO_MASK (1 << 1)
/* Power LED is green - ECO mode */
#define MSI_STANDARD_EC_ECO_MASK (1 << 3)
/* Touchpad is turned on */
#define MSI_STANDARD_EC_TOUCHPAD_MASK (1 << 4)
/* If this bit != bit 1, turbo mode can't be toggled */
#define MSI_STANDARD_EC_TURBO_COOLDOWN_MASK (1 << 7)
#define MSI_STANDARD_EC_FAN_ADDRESS 0x33
/* If zero, fan rotates at maximal speed */
#define MSI_STANDARD_EC_AUTOFAN_MASK (1 << 0)
#ifdef CONFIG_PM_SLEEP
static int msi_laptop_resume(struct device *device);
#endif
static SIMPLE_DEV_PM_OPS(msi_laptop_pm, NULL, msi_laptop_resume);
#define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f
static bool force;
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
static int auto_brightness;
module_param(auto_brightness, int, 0);
MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
static const struct key_entry msi_laptop_keymap[] = {
{KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} }, /* Touch Pad On */
{KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },/* Touch Pad On */
{KE_END, 0}
};
static struct input_dev *msi_laptop_input_dev;
static int wlan_s, bluetooth_s, threeg_s;
static int threeg_exists;
static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
/* MSI laptop quirks */
struct quirk_entry {
bool old_ec_model;
/* Some MSI 3G netbook only have one fn key to control
* Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to
* disable the original Wlan/Bluetooth control by BIOS when user press
* fn key, then control Wlan/Bluetooth/3G by SCM (software control by
* OS). Without SCM, user cann't on/off 3G module on those 3G netbook.
* On Linux, msi-laptop driver will do the same thing to disable the
* original BIOS control, then might need use HAL or other userland
* application to do the software control that simulate with SCM.
* e.g. MSI N034 netbook
*/
bool load_scm_model;
/* Some MSI laptops need delay before reading from EC */
bool ec_delay;
/* Some MSI Wind netbooks (e.g. MSI Wind U100) need loading SCM to get
* some features working (e.g. ECO mode), but we cannot change
* Wlan/Bluetooth state in software and we can only read its state.
*/
bool ec_read_only;
};
static struct quirk_entry *quirks;
/* Hardware access */
static int set_lcd_level(int level)
{
u8 buf[2];
if (level < 0 || level >= MSI_LCD_LEVEL_MAX)
return -EINVAL;
buf[0] = 0x80;
buf[1] = (u8) (level*31);
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf),
NULL, 0);
}
static int get_lcd_level(void)
{
u8 wdata = 0