|
@@ -58,6 +58,7 @@
|
|
|
#include <linux/dmi.h>
|
|
|
#include <linux/backlight.h>
|
|
|
#include <linux/platform_device.h>
|
|
|
+#include <linux/rfkill.h>
|
|
|
|
|
|
#define MSI_DRIVER_VERSION "0.5"
|
|
|
|
|
@@ -66,6 +67,20 @@
|
|
|
#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)
|
|
|
+
|
|
|
+static int msi_laptop_resume(struct platform_device *device);
|
|
|
+
|
|
|
+#define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f
|
|
|
+
|
|
|
static int force;
|
|
|
module_param(force, bool, 0);
|
|
|
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
|
|
@@ -74,6 +89,23 @@ 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 bool old_ec_model;
|
|
|
+static int wlan_s, bluetooth_s, threeg_s;
|
|
|
+static int threeg_exists;
|
|
|
+
|
|
|
+/* 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
|
|
|
+ */
|
|
|
+static bool load_scm_model;
|
|
|
+static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
|
|
|
+
|
|
|
/* Hardware access */
|
|
|
|
|
|
static int set_lcd_level(int level)
|
|
@@ -130,6 +162,35 @@ static int set_auto_brightness(int enable)
|
|
|
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1);
|
|
|
}
|
|
|
|
|
|
+static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
|
|
|
+{
|
|
|
+ int status;
|
|
|
+ u8 wdata = 0, rdata;
|
|
|
+ int result;
|
|
|
+
|
|
|
+ if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* read current device state */
|
|
|
+ result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
|
|
|
+ if (result < 0)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!!(rdata & mask) != status) {
|
|
|
+ /* reverse device bit */
|
|
|
+ if (rdata & mask)
|
|
|
+ wdata = rdata & ~mask;
|
|
|
+ else
|
|
|
+ wdata = rdata | mask;
|
|
|
+
|
|
|
+ result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata);
|
|
|
+ if (result < 0)
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
static int get_wireless_state(int *wlan, int *bluetooth)
|
|
|
{
|
|
|
u8 wdata = 0, rdata;
|
|
@@ -148,6 +209,38 @@ static int get_wireless_state(int *wlan, int *bluetooth)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int get_wireless_state_ec_standard(void)
|
|
|
+{
|
|
|
+ u8 rdata;
|
|
|
+ int result;
|
|
|
+
|
|
|
+ result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
|
|
|
+ if (result < 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK);
|
|
|
+
|
|
|
+ bluetooth_s = !!(rdata & MSI_STANDARD_EC_BLUETOOTH_MASK);
|
|
|
+
|
|
|
+ threeg_s = !!(rdata & MSI_STANDARD_EC_3G_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int get_threeg_exists(void)
|
|
|
+{
|
|
|
+ u8 rdata;
|
|
|
+ int result;
|
|
|
+
|
|
|
+ result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata);
|
|
|
+ if (result < 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/* Backlight device stuff */
|
|
|
|
|
|
static int bl_get_brightness(struct backlight_device *b)
|
|
@@ -176,26 +269,71 @@ static ssize_t show_wlan(struct device *dev,
|
|
|
|
|
|
int ret, enabled;
|
|
|
|
|
|
- ret = get_wireless_state(&enabled, NULL);
|
|
|
+ if (old_ec_model) {
|
|
|
+ ret = get_wireless_state(&enabled, NULL);
|
|
|
+ } else {
|
|
|
+ ret = get_wireless_state_ec_standard();
|
|
|
+ enabled = wlan_s;
|
|
|
+ }
|
|
|
if (ret < 0)
|
|
|
return ret;
|
|
|
|
|
|
return sprintf(buf, "%i\n", enabled);
|
|
|
}
|
|
|
|
|
|
+static ssize_t store_wlan(struct device *dev,
|
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
|
+{
|
|
|
+ return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK);
|
|
|
+}
|
|
|
+
|
|
|
static ssize_t show_bluetooth(struct device *dev,
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
{
|
|
|
|
|
|
int ret, enabled;
|
|
|
|
|
|
- ret = get_wireless_state(NULL, &enabled);
|
|
|
+ if (old_ec_model) {
|
|
|
+ ret = get_wireless_state(NULL, &enabled);
|
|
|
+ } else {
|
|
|
+ ret = get_wireless_state_ec_standard();
|
|
|
+ enabled = bluetooth_s;
|
|
|
+ }
|
|
|
if (ret < 0)
|
|
|
return ret;
|
|
|
|
|
|
return sprintf(buf, "%i\n", enabled);
|
|
|
}
|
|
|
|
|
|
+static ssize_t store_bluetooth(struct device *dev,
|
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
|
+{
|
|
|
+ return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t show_threeg(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* old msi ec not support 3G */
|
|
|
+ if (old_ec_model)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ ret = get_wireless_state_ec_standard();
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return sprintf(buf, "%i\n", threeg_s);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t store_threeg(struct device *dev,
|
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
|
+{
|
|
|
+ return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK);
|
|
|
+}
|
|
|
+
|
|
|
static ssize_t show_lcd_level(struct device *dev,
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
{
|
|
@@ -258,6 +396,7 @@ static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
|
|
|
static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, store_auto_brightness);
|
|
|
static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
|
|
|
static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
|
|
|
+static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
|
|
|
|
|
|
static struct attribute *msipf_attributes[] = {
|
|
|
&dev_attr_lcd_level.attr,
|
|
@@ -275,7 +414,8 @@ static struct platform_driver msipf_driver = {
|
|
|
.driver = {
|
|
|
.name = "msi-laptop-pf",
|
|
|
.owner = THIS_MODULE,
|
|
|
- }
|
|
|
+ },
|
|
|
+ .resume = msi_laptop_resume,
|
|
|
};
|
|
|
|
|
|
static struct platform_device *msipf_device;
|
|
@@ -332,6 +472,192 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
|
|
|
{ }
|
|
|
};
|
|
|
|
|
|
+static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
|
|
|
+ {
|
|
|
+ .ident = "MSI N034",
|
|
|
+ .matches = {
|
|
|
+ DMI_MATCH(DMI_SYS_VENDOR,
|
|
|
+ "MICRO-STAR INTERNATIONAL CO., LTD"),
|
|
|
+ DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034"),
|
|
|
+ DMI_MATCH(DMI_CHASSIS_VENDOR,
|
|
|
+ "MICRO-STAR INTERNATIONAL CO., LTD")
|
|
|
+ },
|
|
|
+ .callback = dmi_check_cb
|
|
|
+ },
|
|
|
+ { }
|
|
|
+};
|
|
|
+
|
|
|
+static int rfkill_bluetooth_set(void *data, bool blocked)
|
|
|
+{
|
|
|
+ /* Do something with blocked...*/
|
|
|
+ /*
|
|
|
+ * blocked == false is on
|
|
|
+ * blocked == true is off
|
|
|
+ */
|
|
|
+ if (blocked)
|
|
|
+ set_device_state("0", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
|
|
|
+ else
|
|
|
+ set_device_state("1", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int rfkill_wlan_set(void *data, bool blocked)
|
|
|
+{
|
|
|
+ if (blocked)
|
|
|
+ set_device_state("0", 0, MSI_STANDARD_EC_WLAN_MASK);
|
|
|
+ else
|
|
|
+ set_device_state("1", 0, MSI_STANDARD_EC_WLAN_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int rfkill_threeg_set(void *data, bool blocked)
|
|
|
+{
|
|
|
+ if (blocked)
|
|
|
+ set_device_state("0", 0, MSI_STANDARD_EC_3G_MASK);
|
|
|
+ else
|
|
|
+ set_device_state("1", 0, MSI_STANDARD_EC_3G_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct rfkill_ops rfkill_bluetooth_ops = {
|
|
|
+ .set_block = rfkill_bluetooth_set
|
|
|
+};
|
|
|
+
|
|
|
+static struct rfkill_ops rfkill_wlan_ops = {
|
|
|
+ .set_block = rfkill_wlan_set
|
|
|
+};
|
|
|
+
|
|
|
+static struct rfkill_ops rfkill_threeg_ops = {
|
|
|
+ .set_block = rfkill_threeg_set
|
|
|
+};
|
|
|
+
|
|
|
+static void rfkill_cleanup(void)
|
|
|
+{
|
|
|
+ if (rfk_bluetooth) {
|
|
|
+ rfkill_unregister(rfk_bluetooth);
|
|
|
+ rfkill_destroy(rfk_bluetooth);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rfk_threeg) {
|
|
|
+ rfkill_unregister(rfk_threeg);
|
|
|
+ rfkill_destroy(rfk_threeg);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rfk_wlan) {
|
|
|
+ rfkill_unregister(rfk_wlan);
|
|
|
+ rfkill_destroy(rfk_wlan);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int rfkill_init(struct platform_device *sdev)
|
|
|
+{
|
|
|
+ /* add rfkill */
|
|
|
+ int retval;
|
|
|
+
|
|
|
+ rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev,
|
|
|
+ RFKILL_TYPE_BLUETOOTH,
|
|
|
+ &rfkill_bluetooth_ops, NULL);
|
|
|
+ if (!rfk_bluetooth) {
|
|
|
+ retval = -ENOMEM;
|
|
|
+ goto err_bluetooth;
|
|
|
+ }
|
|
|
+ retval = rfkill_register(rfk_bluetooth);
|
|
|
+ if (retval)
|
|
|
+ goto err_bluetooth;
|
|
|
+
|
|
|
+ rfk_wlan = rfkill_alloc("msi-wlan", &sdev->dev, RFKILL_TYPE_WLAN,
|
|
|
+ &rfkill_wlan_ops, NULL);
|
|
|
+ if (!rfk_wlan) {
|
|
|
+ retval = -ENOMEM;
|
|
|
+ goto err_wlan;
|
|
|
+ }
|
|
|
+ retval = rfkill_register(rfk_wlan);
|
|
|
+ if (retval)
|
|
|
+ goto err_wlan;
|
|
|
+
|
|
|
+ if (threeg_exists) {
|
|
|
+ rfk_threeg = rfkill_alloc("msi-threeg", &sdev->dev,
|
|
|
+ RFKILL_TYPE_WWAN, &rfkill_threeg_ops, NULL);
|
|
|
+ if (!rfk_threeg) {
|
|
|
+ retval = -ENOMEM;
|
|
|
+ goto err_threeg;
|
|
|
+ }
|
|
|
+ retval = rfkill_register(rfk_threeg);
|
|
|
+ if (retval)
|
|
|
+ goto err_threeg;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err_threeg:
|
|
|
+ rfkill_destroy(rfk_threeg);
|
|
|
+ if (rfk_wlan)
|
|
|
+ rfkill_unregister(rfk_wlan);
|
|
|
+err_wlan:
|
|
|
+ rfkill_destroy(rfk_wlan);
|
|
|
+ if (rfk_bluetooth)
|
|
|
+ rfkill_unregister(rfk_bluetooth);
|
|
|
+err_bluetooth:
|
|
|
+ rfkill_destroy(rfk_bluetooth);
|
|
|
+
|
|
|
+ return retval;
|
|
|
+}
|
|
|
+
|
|
|
+static int msi_laptop_resume(struct platform_device *device)
|
|
|
+{
|
|
|
+ u8 data;
|
|
|
+ int result;
|
|
|
+
|
|
|
+ if (!load_scm_model)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /* set load SCM to disable hardware control by fn key */
|
|
|
+ result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
|
|
|
+ data | MSI_STANDARD_EC_SCM_LOAD_MASK);
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int load_scm_model_init(struct platform_device *sdev)
|
|
|
+{
|
|
|
+ u8 data;
|
|
|
+ int result;
|
|
|
+
|
|
|
+ /* allow userland write sysfs file */
|
|
|
+ dev_attr_bluetooth.store = store_bluetooth;
|
|
|
+ dev_attr_wlan.store = store_wlan;
|
|
|
+ dev_attr_threeg.store = store_threeg;
|
|
|
+ dev_attr_bluetooth.attr.mode |= S_IWUSR;
|
|
|
+ dev_attr_wlan.attr.mode |= S_IWUSR;
|
|
|
+ dev_attr_threeg.attr.mode |= S_IWUSR;
|
|
|
+
|
|
|
+ /* disable hardware control by fn key */
|
|
|
+ result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
|
|
|
+ data | MSI_STANDARD_EC_SCM_LOAD_MASK);
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ /* initial rfkill */
|
|
|
+ result = rfkill_init(sdev);
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int __init msi_init(void)
|
|
|
{
|
|
|
int ret;
|
|
@@ -339,8 +665,14 @@ static int __init msi_init(void)
|
|
|
if (acpi_disabled)
|
|
|
return -ENODEV;
|
|
|
|
|
|
- if (!force && !dmi_check_system(msi_dmi_table))
|
|
|
- return -ENODEV;
|
|
|
+ if (force || dmi_check_system(msi_dmi_table))
|
|
|
+ old_ec_model = 1;
|
|
|
+
|
|
|
+ if (!old_ec_model)
|
|
|
+ get_threeg_exists();
|
|
|
+
|
|
|
+ if (!old_ec_model && dmi_check_system(msi_load_scm_models_dmi_table))
|
|
|
+ load_scm_model = 1;
|
|
|
|
|
|
if (auto_brightness < 0 || auto_brightness > 2)
|
|
|
return -EINVAL;
|
|
@@ -374,10 +706,23 @@ static int __init msi_init(void)
|
|
|
if (ret)
|
|
|
goto fail_platform_device1;
|
|
|
|
|
|
+ if (load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto fail_platform_device1;
|
|
|
+ }
|
|
|
+
|
|
|
ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
|
|
|
if (ret)
|
|
|
goto fail_platform_device2;
|
|
|
|
|
|
+ if (!old_ec_model) {
|
|
|
+ if (threeg_exists)
|
|
|
+ ret = device_create_file(&msipf_device->dev,
|
|
|
+ &dev_attr_threeg);
|
|
|
+ if (ret)
|
|
|
+ goto fail_platform_device2;
|
|
|
+ }
|
|
|
+
|
|
|
/* Disable automatic brightness control by default because
|
|
|
* this module was probably loaded to do brightness control in
|
|
|
* software. */
|
|
@@ -412,10 +757,14 @@ static void __exit msi_cleanup(void)
|
|
|
{
|
|
|
|
|
|
sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
|
|
|
+ if (!old_ec_model && threeg_exists)
|
|
|
+ device_remove_file(&msipf_device->dev, &dev_attr_threeg);
|
|
|
platform_device_unregister(msipf_device);
|
|
|
platform_driver_unregister(&msipf_driver);
|
|
|
backlight_device_unregister(msibl_device);
|
|
|
|
|
|
+ rfkill_cleanup();
|
|
|
+
|
|
|
/* Enable automatic brightness control again */
|
|
|
if (auto_brightness != 2)
|
|
|
set_auto_brightness(1);
|
|
@@ -435,3 +784,4 @@ MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARIN
|
|
|
MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*");
|
|
|
MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
|
|
|
MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
|
|
|
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
|