#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/backlight.h>
+#include <linux/leds.h>
#include <linux/fb.h>
#include <linux/dmi.h>
#include <linux/platform_device.h>
#include <linux/rfkill.h>
#include <linux/acpi.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/ctype.h>
+#if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE)
+#include <acpi/video.h>
+#endif
/*
* This driver is needed because a number of Samsung laptops do not hook
#define SABI_IFACE_COMPLETE 0x04
#define SABI_IFACE_DATA 0x05
+#define WL_STATUS_WLAN 0x0
+#define WL_STATUS_BT 0x2
+
/* Structure get/set data using sabi */
struct sabi_data {
union {
u16 get_performance_level;
u16 set_performance_level;
+ /* 0x80 is off, 0x81 is on */
+ u16 get_battery_life_extender;
+ u16 set_battery_life_extender;
+
+ /* 0x80 is off, 0x81 is on */
+ u16 get_usb_charge;
+ u16 set_usb_charge;
+
+ /* the first byte is for bluetooth and the third one is for wlan */
+ u16 get_wireless_status;
+ u16 set_wireless_status;
+
+ /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
+ u16 kbd_backlight;
+
/*
* Tell the BIOS that Linux is running on this machine.
* 81 is on, 80 is off
};
struct sabi_config {
+ int sabi_version;
const char *test_string;
u16 main_function;
const struct sabi_header_offsets header_offsets;
static const struct sabi_config sabi_configs[] = {
{
+ /* I don't know if it is really 2, but it it is
+ * less than 3 anyway */
+ .sabi_version = 2,
+
.test_string = "SECLINUX",
.main_function = 0x4c49,
.get_performance_level = 0x08,
.set_performance_level = 0x09,
+ .get_battery_life_extender = 0xFFFF,
+ .set_battery_life_extender = 0xFFFF,
+
+ .get_usb_charge = 0xFFFF,
+ .set_usb_charge = 0xFFFF,
+
+ .get_wireless_status = 0xFFFF,
+ .set_wireless_status = 0xFFFF,
+
+ .kbd_backlight = 0xFFFF,
+
.set_linux = 0x0a,
},
.max_brightness = 8,
},
{
+ .sabi_version = 3,
+
.test_string = "SwSmi@",
.main_function = 0x5843,
.get_performance_level = 0x31,
.set_performance_level = 0x32,
+ .get_battery_life_extender = 0x65,
+ .set_battery_life_extender = 0x66,
+
+ .get_usb_charge = 0x67,
+ .set_usb_charge = 0x68,
+
+ .get_wireless_status = 0x69,
+ .set_wireless_status = 0x6a,
+
+ .kbd_backlight = 0x78,
+
.set_linux = 0xff,
},
{ },
};
+/*
+ * samsung-laptop/ - debugfs root directory
+ * f0000_segment - dump f0000 segment
+ * command - current command
+ * data - current data
+ * d0, d1, d2, d3 - data fields
+ * call - call SABI using command and data
+ *
+ * This allow to call arbitrary sabi commands wihout
+ * modifying the driver at all.
+ * For example, setting the keyboard backlight brightness to 5
+ *
+ * echo 0x78 > command
+ * echo 0x0582 > d0
+ * echo 0 > d1
+ * echo 0 > d2
+ * echo 0 > d3
+ * cat call
+ */
+
+struct samsung_laptop_debug {
+ struct dentry *root;
+ struct sabi_data data;
+ u16 command;
+
+ struct debugfs_blob_wrapper f0000_wrapper;
+ struct debugfs_blob_wrapper data_wrapper;
+ struct debugfs_blob_wrapper sdiag_wrapper;
+};
+
+struct samsung_laptop;
+
+struct samsung_rfkill {
+ struct samsung_laptop *samsung;
+ struct rfkill *rfkill;
+ enum rfkill_type type;
+};
+
struct samsung_laptop {
const struct sabi_config *config;
struct platform_device *platform_device;
struct backlight_device *backlight_device;
- struct rfkill *rfk;
+
+ struct samsung_rfkill wlan;
+ struct samsung_rfkill bluetooth;
+
+ struct led_classdev kbd_led;
+ int kbd_led_wk;
+ struct workqueue_struct *led_workqueue;
+ struct work_struct kbd_led_work;
+
+ struct samsung_laptop_debug debug;
+ struct samsung_quirks *quirks;
bool handle_backlight;
bool has_stepping_quirk;
+
+ char sdiag[64];
};
+struct samsung_quirks {
+ bool broken_acpi_video;
+};
+static struct samsung_quirks samsung_unknown = {};
+
+static struct samsung_quirks samsung_broken_acpi_video = {
+ .broken_acpi_video = true,
+};
static bool force;
module_param(force, bool, 0);
if (debug) {
if (in)
- pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
+ pr_info("SABI command:0x%04x "
+ "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
command, in->d0, in->d1, in->d2, in->d3);
else
- pr_info("SABI 0x%04x", command);
+ pr_info("SABI command:0x%04x", command);
}
/* enable memory to be able to write to it */
/* see if the command actually succeeded */
complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
- if (complete != 0xaa || iface_data == 0xff) {
+
+ /* iface_data = 0xFF happens when a command is not known
+ * so we only add a warning in debug mode since we will
+ * probably issue some unknown command at startup to find
+ * out which features are supported */
+ if (complete != 0xaa || (iface_data == 0xff && debug))
pr_warn("SABI command 0x%04x failed with"
" completion flag 0x%02x and interface data 0x%02x",
command, complete, iface_data);
+
+ if (complete != 0xaa || iface_data == 0xff) {
ret = -EINVAL;
goto exit;
}
}
if (debug && out) {
- pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
+ pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
out->d0, out->d1, out->d2, out->d3);
}
static int sabi_set_commandb(struct samsung_laptop *samsung,
u16 command, u8 data)
{
- struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 };
+ struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
in.data[0] = data;
return sabi_command(samsung, command, &in, NULL);
}
-static void test_backlight(struct samsung_laptop *samsung)
-{
- const struct sabi_commands *commands = &samsung->config->commands;
- struct sabi_data sretval;
-
- sabi_command(samsung, commands->get_backlight, NULL, &sretval);
- printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]);
-
- sabi_set_commandb(samsung, commands->set_backlight, 0);
- printk(KERN_DEBUG "backlight should be off\n");
-
- sabi_command(samsung, commands->get_backlight, NULL, &sretval);
- printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]);
-
- msleep(1000);
-
- sabi_set_commandb(samsung, commands->set_backlight, 1);
- printk(KERN_DEBUG "backlight should be on\n");
-
- sabi_command(samsung, commands->get_backlight, NULL, &sretval);
- printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]);
-}
-
-static void test_wireless(struct samsung_laptop *samsung)
-{
- const struct sabi_commands *commands = &samsung->config->commands;
- struct sabi_data sretval;
-
- sabi_command(samsung, commands->get_wireless_button, NULL, &sretval);
- printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]);
-
- sabi_set_commandb(samsung, commands->set_wireless_button, 0);
- printk(KERN_DEBUG "wireless led should be off\n");
-
- sabi_command(samsung, commands->get_wireless_button, NULL, &sretval);
- printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]);
-
- msleep(1000);
-
- sabi_set_commandb(samsung, commands->set_wireless_button, 1);
- printk(KERN_DEBUG "wireless led should be on\n");
-
- sabi_command(samsung, commands->get_wireless_button, NULL, &sretval);
- printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]);
-}
-
static int read_brightness(struct samsung_laptop *samsung)
{
const struct sabi_config *config = samsung->config;
.update_status = update_status,
};
-static int rfkill_set(void *data, bool blocked)
+static int seclinux_rfkill_set(void *data, bool blocked)
{
- struct samsung_laptop *samsung = data;
+ struct samsung_rfkill *srfkill = data;
+ struct samsung_laptop *samsung = srfkill->samsung;
const struct sabi_commands *commands = &samsung->config->commands;
- /* Do something with blocked...*/
- /*
- * blocked == false is on
- * blocked == true is off
- */
- if (blocked)
- sabi_set_commandb(samsung, commands->set_wireless_button, 0);
+ return sabi_set_commandb(samsung, commands->set_wireless_button,
+ !blocked);
+}
+
+static struct rfkill_ops seclinux_rfkill_ops = {
+ .set_block = seclinux_rfkill_set,
+};
+
+static int swsmi_wireless_status(struct samsung_laptop *samsung,
+ struct sabi_data *data)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+
+ return sabi_command(samsung, commands->get_wireless_status,
+ NULL, data);
+}
+
+static int swsmi_rfkill_set(void *priv, bool blocked)
+{
+ struct samsung_rfkill *srfkill = priv;
+ struct samsung_laptop *samsung = srfkill->samsung;
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int ret, i;
+
+ ret = swsmi_wireless_status(samsung, &data);
+ if (ret)
+ return ret;
+
+ /* Don't set the state for non-present devices */
+ for (i = 0; i < 4; i++)
+ if (data.data[i] == 0x02)
+ data.data[1] = 0;
+
+ if (srfkill->type == RFKILL_TYPE_WLAN)
+ data.data[WL_STATUS_WLAN] = !blocked;
+ else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
+ data.data[WL_STATUS_BT] = !blocked;
+
+ return sabi_command(samsung, commands->set_wireless_status,
+ &data, &data);
+}
+
+static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
+{
+ struct samsung_rfkill *srfkill = priv;
+ struct samsung_laptop *samsung = srfkill->samsung;
+ struct sabi_data data;
+ int ret;
+
+ ret = swsmi_wireless_status(samsung, &data);
+ if (ret)
+ return ;
+
+ if (srfkill->type == RFKILL_TYPE_WLAN)
+ ret = data.data[WL_STATUS_WLAN];
+ else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
+ ret = data.data[WL_STATUS_BT];
else
- sabi_set_commandb(samsung, commands->set_wireless_button, 1);
+ return ;
- return 0;
+ rfkill_set_sw_state(rfkill, !ret);
}
-static struct rfkill_ops rfkill_ops = {
- .set_block = rfkill_set,
+static struct rfkill_ops swsmi_rfkill_ops = {
+ .set_block = swsmi_rfkill_set,
+ .query = swsmi_rfkill_query,
};
static ssize_t get_performance_level(struct device *dev,
static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
get_performance_level, set_performance_level);
+static int read_battery_life_extender(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int retval;
+
+ if (commands->get_battery_life_extender == 0xFFFF)
+ return -ENODEV;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80;
+ retval = sabi_command(samsung, commands->get_battery_life_extender,
+ &data, &data);
+
+ if (retval)
+ return retval;
+
+ if (data.data[0] != 0 && data.data[0] != 1)
+ return -ENODEV;
+
+ return data.data[0];
+}
+
+static int write_battery_life_extender(struct samsung_laptop *samsung,
+ int enabled)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80 | enabled;
+ return sabi_command(samsung, commands->set_battery_life_extender,
+ &data, NULL);
+}
+
+static ssize_t get_battery_life_extender(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret;
+
+ ret = read_battery_life_extender(samsung);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t set_battery_life_extender(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret, value;
+
+ if (!count || sscanf(buf, "%i", &value) != 1)
+ return -EINVAL;
+
+ ret = write_battery_life_extender(samsung, !!value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
+ get_battery_life_extender, set_battery_life_extender);
+
+static int read_usb_charge(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int retval;
+
+ if (commands->get_usb_charge == 0xFFFF)
+ return -ENODEV;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80;
+ retval = sabi_command(samsung, commands->get_usb_charge,
+ &data, &data);
+
+ if (retval)
+ return retval;
+
+ if (data.data[0] != 0 && data.data[0] != 1)
+ return -ENODEV;
+
+ return data.data[0];
+}
+
+static int write_usb_charge(struct samsung_laptop *samsung,
+ int enabled)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80 | enabled;
+ return sabi_command(samsung, commands->set_usb_charge,
+ &data, NULL);
+}
+
+static ssize_t get_usb_charge(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret;
+
+ ret = read_usb_charge(samsung);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t set_usb_charge(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret, value;
+
+ if (!count || sscanf(buf, "%i", &value) != 1)
+ return -EINVAL;
+
+ ret = write_usb_charge(samsung, !!value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
+ get_usb_charge, set_usb_charge);
+
static struct attribute *platform_attributes[] = {
&dev_attr_performance_level.attr,
+ &dev_attr_battery_life_extender.attr,
+ &dev_attr_usb_charge.attr,
NULL
};
static void samsung_rfkill_exit(struct samsung_laptop *samsung)
{
- if (samsung->rfk) {
- rfkill_unregister(samsung->rfk);
- rfkill_destroy(samsung->rfk);
- samsung->rfk = NULL;
+ if (samsung->wlan.rfkill) {
+ rfkill_unregister(samsung->wlan.rfkill);
+ rfkill_destroy(samsung->wlan.rfkill);
+ samsung->wlan.rfkill = NULL;
+ }
+ if (samsung->bluetooth.rfkill) {
+ rfkill_unregister(samsung->bluetooth.rfkill);
+ rfkill_destroy(samsung->bluetooth.rfkill);
+ samsung->bluetooth.rfkill = NULL;
+ }
+}
+
+static int samsung_new_rfkill(struct samsung_laptop *samsung,
+ struct samsung_rfkill *arfkill,
+ const char *name, enum rfkill_type type,
+ const struct rfkill_ops *ops,
+ int blocked)
+{
+ struct rfkill **rfkill = &arfkill->rfkill;
+ int ret;
+
+ arfkill->type = type;
+ arfkill->samsung = samsung;
+
+ *rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
+ type, ops, arfkill);
+
+ if (!*rfkill)
+ return -EINVAL;
+
+ if (blocked != -1)
+ rfkill_init_sw_state(*rfkill, blocked);
+
+ ret = rfkill_register(*rfkill);
+ if (ret) {
+ rfkill_destroy(*rfkill);
+ *rfkill = NULL;
+ return ret;
+ }
+ return 0;
+}
+
+static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
+{
+ return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
+ RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
+}
+
+static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
+{
+ struct sabi_data data;
+ int ret;
+
+ ret = swsmi_wireless_status(samsung, &data);
+ if (ret) {
+ /* Some swsmi laptops use the old seclinux way to control
+ * wireless devices */
+ if (ret == -EINVAL)
+ ret = samsung_rfkill_init_seclinux(samsung);
+ return ret;
}
+
+ /* 0x02 seems to mean that the device is no present/available */
+
+ if (data.data[WL_STATUS_WLAN] != 0x02)
+ ret = samsung_new_rfkill(samsung, &samsung->wlan,
+ "samsung-wlan",
+ RFKILL_TYPE_WLAN,
+ &swsmi_rfkill_ops,
+ !data.data[WL_STATUS_WLAN]);
+ if (ret)
+ goto exit;
+
+ if (data.data[WL_STATUS_BT] != 0x02)
+ ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
+ "samsung-bluetooth",
+ RFKILL_TYPE_BLUETOOTH,
+ &swsmi_rfkill_ops,
+ !data.data[WL_STATUS_BT]);
+ if (ret)
+ goto exit;
+
+exit:
+ if (ret)
+ samsung_rfkill_exit(samsung);
+
+ return ret;
}
static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
{
+ if (samsung->config->sabi_version == 2)
+ return samsung_rfkill_init_seclinux(samsung);
+ if (samsung->config->sabi_version == 3)
+ return samsung_rfkill_init_swsmi(samsung);
+ return 0;
+}
+
+static int kbd_backlight_enable(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
int retval;
- samsung->rfk = rfkill_alloc("samsung-wifi",
- &samsung->platform_device->dev,
- RFKILL_TYPE_WLAN,
- &rfkill_ops, samsung);
- if (!samsung->rfk)
- return -ENOMEM;
+ if (commands->kbd_backlight == 0xFFFF)
+ return -ENODEV;
+
+ memset(&data, 0, sizeof(data));
+ data.d0 = 0xaabb;
+ retval = sabi_command(samsung, commands->kbd_backlight,
+ &data, &data);
+
+ if (retval)
+ return retval;
- retval = rfkill_register(samsung->rfk);
- if (retval) {
- rfkill_destroy(samsung->rfk);
- samsung->rfk = NULL;
+ if (data.d0 != 0xccdd)
return -ENODEV;
+ return 0;
+}
+
+static int kbd_backlight_read(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int retval;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x81;
+ retval = sabi_command(samsung, commands->kbd_backlight,
+ &data, &data);
+
+ if (retval)
+ return retval;
+
+ return data.data[0];
+}
+
+static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.d0 = 0x82 | ((brightness & 0xFF) << 8);
+ return sabi_command(samsung, commands->kbd_backlight,
+ &data, NULL);
+}
+
+static void kbd_led_update(struct work_struct *work)
+{
+ struct samsung_laptop *samsung;
+
+ samsung = container_of(work, struct samsung_laptop, kbd_led_work);
+ kbd_backlight_write(samsung, samsung->kbd_led_wk);
+}
+
+static void kbd_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct samsung_laptop *samsung;
+
+ samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
+
+ if (value > samsung->kbd_led.max_brightness)
+ value = samsung->kbd_led.max_brightness;
+ else if (value < 0)
+ value = 0;
+
+ samsung->kbd_led_wk = value;
+ queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
+}
+
+static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
+{
+ struct samsung_laptop *samsung;
+
+ samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
+ return kbd_backlight_read(samsung);
+}
+
+static void samsung_leds_exit(struct samsung_laptop *samsung)
+{
+ if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
+ led_classdev_unregister(&samsung->kbd_led);
+ if (samsung->led_workqueue)
+ destroy_workqueue(samsung->led_workqueue);
+}
+
+static int __init samsung_leds_init(struct samsung_laptop *samsung)
+{
+ int ret = 0;
+
+ samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
+ if (!samsung->led_workqueue)
+ return -ENOMEM;
+
+ if (kbd_backlight_enable(samsung) >= 0) {
+ INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
+
+ samsung->kbd_led.name = "samsung::kbd_backlight";
+ samsung->kbd_led.brightness_set = kbd_led_set;
+ samsung->kbd_led.brightness_get = kbd_led_get;
+ samsung->kbd_led.max_brightness = 8;
+
+ ret = led_classdev_register(&samsung->platform_device->dev,
+ &samsung->kbd_led);
}
- return 0;
+ if (ret)
+ samsung_leds_exit(samsung);
+
+ return ret;
}
static void samsung_backlight_exit(struct samsung_laptop *samsung)
return 0;
}
-static mode_t samsung_sysfs_is_visible(struct kobject *kobj,
+static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
struct attribute *attr, int idx)
{
struct device *dev = container_of(kobj, struct device, kobj);
if (attr == &dev_attr_performance_level.attr)
ok = !!samsung->config->performance_levels[0].name;
+ if (attr == &dev_attr_battery_life_extender.attr)
+ ok = !!(read_battery_life_extender(samsung) >= 0);
+ if (attr == &dev_attr_usb_charge.attr)
+ ok = !!(read_usb_charge(samsung) >= 0);
return ok ? attr->mode : 0;
}
}
+static int show_call(struct seq_file *m, void *data)
+{
+ struct samsung_laptop *samsung = m->private;
+ struct sabi_data *sdata = &samsung->debug.data;
+ int ret;
+
+ seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
+ samsung->debug.command,
+ sdata->d0, sdata->d1, sdata->d2, sdata->d3);
+
+ ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
+
+ if (ret) {
+ seq_printf(m, "SABI command 0x%04x failed\n",
+ samsung->debug.command);
+ return ret;
+ }
+
+ seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
+ sdata->d0, sdata->d1, sdata->d2, sdata->d3);
+ return 0;
+}
+
+static int samsung_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, show_call, inode->i_private);
+}
+
+static const struct file_operations samsung_laptop_call_io_ops = {
+ .owner = THIS_MODULE,
+ .open = samsung_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void samsung_debugfs_exit(struct samsung_laptop *samsung)
+{
+ debugfs_remove_recursive(samsung->debug.root);
+}
+
+static int samsung_debugfs_init(struct samsung_laptop *samsung)
+{
+ struct dentry *dent;
+
+ samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
+ if (!samsung->debug.root) {
+ pr_err("failed to create debugfs directory");
+ goto error_debugfs;
+ }
+
+ samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
+ samsung->debug.f0000_wrapper.size = 0xffff;
+
+ samsung->debug.data_wrapper.data = &samsung->debug.data;
+ samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
+
+ samsung->debug.sdiag_wrapper.data = samsung->sdiag;
+ samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
+
+ dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
+ samsung->debug.root, &samsung->debug.command);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d0);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d1);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d2);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d3);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
+ samsung->debug.root,
+ &samsung->debug.data_wrapper);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
+ samsung->debug.root,
+ &samsung->debug.f0000_wrapper);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
+ samsung->debug.root, samsung,
+ &samsung_laptop_call_io_ops);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR,
+ samsung->debug.root,
+ &samsung->debug.sdiag_wrapper);
+ if (!dent)
+ goto error_debugfs;
+
+ return 0;
+
+error_debugfs:
+ samsung_debugfs_exit(samsung);
+ return -ENOMEM;
+}
+
static void samsung_sabi_exit(struct samsung_laptop *samsung)
{
const struct sabi_config *config = samsung->config;
samsung->config = NULL;
}
-static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca)
+static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
+ unsigned int ifaceP)
{
const struct sabi_config *config = samsung->config;
printk(KERN_DEBUG "This computer supports SABI==%x\n",
loca + 0xf0000 - 6);
+
printk(KERN_DEBUG "SABI header:\n");
printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
readw(samsung->sabi + config->header_offsets.port));
readw(samsung->sabi + config->header_offsets.data_offset));
printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
readw(samsung->sabi + config->header_offsets.data_segment));
+
+ printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
}
-static void __init samsung_sabi_selftest(struct samsung_laptop *samsung,
- unsigned int ifaceP)
+static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
{
- const struct sabi_config *config = samsung->config;
- struct sabi_data sretval;
+ int loca = find_signature(samsung->f0000_segment, "SDiaG@");
+ int i;
- printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
- printk(KERN_DEBUG "sabi_iface = %p\n", samsung->sabi_iface);
+ if (loca == 0xffff)
+ return ;
- if (samsung->handle_backlight)
- test_backlight(samsung);
- test_wireless(samsung);
+ /* Example:
+ * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
+ *
+ * Product name: 90X3A
+ * BIOS Version: 07HL
+ */
+ loca += 1;
+ for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
+ char temp = readb(samsung->f0000_segment + loca);
+
+ if (isalnum(temp) || temp == '/' || temp == '-')
+ samsung->sdiag[i++] = temp;
+ else
+ break ;
+ }
- sabi_command(samsung, config->commands.get_brightness, NULL, &sretval);
- printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.data[0]);
+ if (debug && samsung->sdiag[0])
+ pr_info("sdiag: %s", samsung->sdiag);
}
static int __init samsung_sabi_init(struct samsung_laptop *samsung)
samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
if (!samsung->f0000_segment) {
- pr_err("Can't map the segment at 0xf0000\n");
+ if (debug || force)
+ pr_err("Can't map the segment at 0xf0000\n");
ret = -EINVAL;
goto exit;
}
+ samsung_sabi_diag(samsung);
+
/* Try to find one of the signatures in memory to find the header */
for (i = 0; sabi_configs[i].test_string != 0; ++i) {
samsung->config = &sabi_configs[i];
}
if (loca == 0xffff) {
- pr_err("This computer does not support SABI\n");
+ if (debug || force)
+ pr_err("This computer does not support SABI\n");
ret = -ENODEV;
goto exit;
}
loca += 1;
samsung->sabi = (samsung->f0000_segment + loca);
- if (debug)
- samsung_sabi_infos(samsung, loca);
-
/* Get a pointer to the SABI Interface */
ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
+
+ if (debug)
+ samsung_sabi_infos(samsung, loca, ifaceP);
+
samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
if (!samsung->sabi_iface) {
pr_err("Can't remap %x\n", ifaceP);
goto exit;
}
- if (debug)
- samsung_sabi_selftest(samsung, ifaceP);
-
/* Turn on "Linux" mode in the BIOS */
if (commands->set_linux != 0xff) {
int retval = sabi_set_commandb(samsung,
if (samsung->handle_backlight)
check_for_stepping_quirk(samsung);
+ pr_info("detected SABI interface: %s\n",
+ samsung->config->test_string);
+
exit:
if (ret)
samsung_sabi_exit(samsung);
return 0;
}
-static int __init dmi_check_cb(const struct dmi_system_id *id)
+static struct samsung_quirks *quirks;
+
+static int __init samsung_dmi_matched(const struct dmi_system_id *d)
{
- pr_info("found laptop model '%s'\n", id->ident);
- return 1;
+ quirks = d->driver_data;
+ return 0;
}
static struct dmi_system_id __initdata samsung_dmi_table[] = {
{
- .ident = "N128",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR,
"SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
- DMI_MATCH(DMI_BOARD_NAME, "N128"),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
},
- .callback = dmi_check_cb,
},
{
- .ident = "N130",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR,
"SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
- DMI_MATCH(DMI_BOARD_NAME, "N130"),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
},
- .callback = dmi_check_cb,
},
{
- .ident = "N510",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR,
"SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
- DMI_MATCH(DMI_BOARD_NAME, "N510"),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
},
- .callback = dmi_check_cb,
},
{
- .ident = "X125",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR,
"SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
- DMI_MATCH(DMI_BOARD_NAME, "X125"),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
},
- .callback = dmi_check_cb,
},
+ /* Specific DMI ids for laptop with quirks */
{
- .ident = "X120/X170",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
- DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
+ .callback = samsung_dmi_matched,
+ .ident = "N150P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
+ DMI_MATCH(DMI_BOARD_NAME, "N150P"),
},
- .callback = dmi_check_cb,
+ .driver_data = &samsung_broken_acpi_video,
},
{
- .ident = "NC10",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
- DMI_MATCH(DMI_BOARD_NAME, "NC10"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "NP-Q45",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
- DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "X360",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
- DMI_MATCH(DMI_BOARD_NAME, "X360"),
+ .callback = samsung_dmi_matched,
+ .ident = "N145P/N250P/N260P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
+ DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
},
- .callback = dmi_check_cb,
+ .driver_data = &samsung_broken_acpi_video,
},
{
- .ident = "R410 Plus",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
- DMI_MATCH(DMI_BOARD_NAME, "R460"),
+ .callback = samsung_dmi_matched,
+ .ident = "N150/N210/N220",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
+ DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
},
- .callback = dmi_check_cb,
+ .driver_data = &samsung_broken_acpi_video,
},
{
- .ident = "R518",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
- DMI_MATCH(DMI_BOARD_NAME, "R518"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "R519/R719",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
- DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
+ .callback = samsung_dmi_matched,
+ .ident = "NF110/NF210/NF310",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
+ DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
},
- .callback = dmi_check_cb,
- },
- {
- .ident = "N150/N210/N220",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
- DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "N220",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
- DMI_MATCH(DMI_BOARD_NAME, "N220"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "N150/N210/N220/N230",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
- DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "N150P/N210P/N220P",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
- DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "R700",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
- DMI_MATCH(DMI_BOARD_NAME, "SR700"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "R530/R730",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
- DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "NF110/NF210/NF310",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
- DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "N145P/N250P/N260P",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
- DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "R70/R71",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR,
- "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
- DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "P460",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
- DMI_MATCH(DMI_BOARD_NAME, "P460"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "R528/R728",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
- DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "NC210/NC110",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
- DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
- },
- .callback = dmi_check_cb,
- },
- {
- .ident = "X520",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
- DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
- DMI_MATCH(DMI_BOARD_NAME, "X520"),
- },
- .callback = dmi_check_cb,
+ .driver_data = &samsung_broken_acpi_video,
},
{ },
};
struct samsung_laptop *samsung;
int ret;
+ quirks = &samsung_unknown;
if (!force && !dmi_check_system(samsung_dmi_table))
return -ENODEV;
mutex_init(&samsung->sabi_mutex);
samsung->handle_backlight = true;
+ samsung->quirks = quirks;
-#ifdef CONFIG_ACPI
+
+#if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE)
/* Don't handle backlight here if the acpi video already handle it */
if (acpi_video_backlight_support()) {
- pr_info("Backlight controlled by ACPI video driver\n");
- samsung->handle_backlight = false;
+ if (samsung->quirks->broken_acpi_video) {
+ pr_info("Disabling ACPI video driver\n");
+ acpi_video_unregister();
+ } else {
+ samsung->handle_backlight = false;
+ }
}
#endif
if (ret)
goto error_sabi;
+#ifdef CONFIG_ACPI
+ /* Only log that if we are really on a sabi platform */
+ if (acpi_video_backlight_support() &&
+ !samsung->quirks->broken_acpi_video)
+ pr_info("Backlight controlled by ACPI video driver\n");
+#endif
+
ret = samsung_sysfs_init(samsung);
if (ret)
goto error_sysfs;
if (ret)
goto error_rfkill;
+ ret = samsung_leds_init(samsung);
+ if (ret)
+ goto error_leds;
+
+ ret = samsung_debugfs_init(samsung);
+ if (ret)
+ goto error_debugfs;
+
samsung_platform_device = samsung->platform_device;
return ret;
+error_debugfs:
+ samsung_leds_exit(samsung);
+error_leds:
+ samsung_rfkill_exit(samsung);
error_rfkill:
samsung_backlight_exit(samsung);
error_backlight:
samsung = platform_get_drvdata(samsung_platform_device);
+ samsung_debugfs_exit(samsung);
+ samsung_leds_exit(samsung);
samsung_rfkill_exit(samsung);
samsung_backlight_exit(samsung);
samsung_sysfs_exit(samsung);