diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a26655881e6a..df89367032c3 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -111,6 +111,21 @@ config ASUS_LAPTOP If you have an ACPI-compatible ASUS laptop, say Y or M here. +config FUJITSU_LAPTOP + tristate "Fujitsu Laptop Extras" + depends on X86 + depends on ACPI + depends on BACKLIGHT_CLASS_DEVICE + ---help--- + This is a driver for laptops built by Fujitsu: + + * P2xxx/P5xxx/S6xxx/S7xxx series Lifebooks + * Possibly other Fujitsu laptop models + + It adds support for LCD brightness control. + + If you have a Fujitsu laptop, say Y or M here. + config MSI_LAPTOP tristate "MSI Laptop Extras" depends on X86 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index b5ce0e3dba86..be90d483d2f9 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -14,4 +14,5 @@ obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o +obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o diff --git a/drivers/misc/fujitsu-laptop.c b/drivers/misc/fujitsu-laptop.c new file mode 100644 index 000000000000..d366a6cc1fd9 --- /dev/null +++ b/drivers/misc/fujitsu-laptop.c @@ -0,0 +1,358 @@ +/*-*-linux-c-*-*/ + +/* + Copyright (C) 2007 Jonathan Woithe + Based on earlier work: + Copyright (C) 2003 Shane Spencer + Adrian Yee + + Templated from msi-laptop.c which is copyright by its respective authors. + + 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. + */ + +/* + * fujitsu-laptop.c - Fujitsu laptop support, providing access to additional + * features made available on a range of Fujitsu laptops including the + * P2xxx/P5xxx/S6xxx/S7xxx series. + * + * This driver exports a few files in /sys/devices/platform/fujitsu-laptop/; + * others may be added at a later date. + * + * lcd_level - Screen brightness: contains a single integer in the + * range 0..7. (rw) + * + * 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/fujitsu-laptop/. + * + * This driver has been tested on a Fujitsu Lifebook S7020. It should + * work on most P-series and S-series Lifebooks, but YMMV. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUJITSU_DRIVER_VERSION "0.3" + +#define FUJITSU_LCD_N_LEVELS 8 + +#define ACPI_FUJITSU_CLASS "fujitsu" +#define ACPI_FUJITSU_HID "FUJ02B1" +#define ACPI_FUJITSU_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI extras driver" +#define ACPI_FUJITSU_DEVICE_NAME "Fujitsu FUJ02B1" + +struct fujitsu_t { + acpi_handle acpi_handle; + struct backlight_device *bl_device; + struct platform_device *pf_device; + + unsigned long fuj02b1_state; + unsigned int brightness_changed; + unsigned int brightness_level; +}; + +static struct fujitsu_t *fujitsu; + +/* Hardware access */ + +static int set_lcd_level(int level) +{ + acpi_status status = AE_OK; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list arg_list = { 1, &arg0 }; + acpi_handle handle = NULL; + + if (level < 0 || level >= FUJITSU_LCD_N_LEVELS) + return -EINVAL; + + if (!fujitsu) + return -EINVAL; + + status = acpi_get_handle(fujitsu->acpi_handle, "SBLL", &handle); + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "SBLL not present\n")); + return -ENODEV; + } + + arg0.integer.value = level; + + status = acpi_evaluate_object(handle, NULL, &arg_list, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return 0; +} + +static int get_lcd_level(void) +{ + unsigned long state = 0; + acpi_status status = AE_OK; + + // Get the Brightness + status = + acpi_evaluate_integer(fujitsu->acpi_handle, "GBLL", NULL, &state); + if (status < 0) + return status; + + fujitsu->fuj02b1_state = state; + fujitsu->brightness_level = state & 0x0fffffff; + + if (state & 0x80000000) + fujitsu->brightness_changed = 1; + else + fujitsu->brightness_changed = 0; + + if (status < 0) + return status; + + return fujitsu->brightness_level; +} + +/* Backlight device stuff */ + +static int bl_get_brightness(struct backlight_device *b) +{ + return get_lcd_level(); +} + +static int bl_update_status(struct backlight_device *b) +{ + return set_lcd_level(b->props.brightness); +} + +static struct backlight_ops fujitsubl_ops = { + .get_brightness = bl_get_brightness, + .update_status = bl_update_status, +}; + +/* Platform device */ + +static ssize_t show_lcd_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + int ret; + + ret = get_lcd_level(); + if (ret < 0) + return ret; + + return sprintf(buf, "%i\n", ret); +} + +static ssize_t store_lcd_level(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + + int level, ret; + + if (sscanf(buf, "%i", &level) != 1 + || (level < 0 || level >= FUJITSU_LCD_N_LEVELS)) + return -EINVAL; + + ret = set_lcd_level(level); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level); + +static struct attribute *fujitsupf_attributes[] = { + &dev_attr_lcd_level.attr, + NULL +}; + +static struct attribute_group fujitsupf_attribute_group = { + .attrs = fujitsupf_attributes +}; + +static struct platform_driver fujitsupf_driver = { + .driver = { + .name = "fujitsu-laptop", + .owner = THIS_MODULE, + } +}; + +/* ACPI device */ + +int acpi_fujitsu_add(struct acpi_device *device) +{ + int result = 0; + int state = 0; + + ACPI_FUNCTION_TRACE("acpi_fujitsu_add"); + + if (!device) + return -EINVAL; + + fujitsu->acpi_handle = device->handle; + sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_DEVICE_NAME); + sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS); + acpi_driver_data(device) = fujitsu; + + result = acpi_bus_get_power(fujitsu->acpi_handle, &state); + if (result) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error reading power state\n")); + goto end; + } + + printk(KERN_INFO PREFIX "%s [%s] (%s)\n", + acpi_device_name(device), acpi_device_bid(device), + !device->power.state ? "on" : "off"); + + end: + + return result; +} + +int acpi_fujitsu_remove(struct acpi_device *device, int type) +{ + ACPI_FUNCTION_TRACE("acpi_fujitsu_remove"); + + if (!device || !acpi_driver_data(device)) + return -EINVAL; + fujitsu->acpi_handle = 0; + + return 0; +} + +static const struct acpi_device_id fujitsu_device_ids[] = { + {ACPI_FUJITSU_HID, 0}, + {"", 0}, +}; + +static struct acpi_driver acpi_fujitsu_driver = { + .name = ACPI_FUJITSU_DRIVER_NAME, + .class = ACPI_FUJITSU_CLASS, + .ids = fujitsu_device_ids, + .ops = { + .add = acpi_fujitsu_add, + .remove = acpi_fujitsu_remove, + }, +}; + +/* Initialization */ + +static int __init fujitsu_init(void) +{ + int ret, result; + + if (acpi_disabled) + return -ENODEV; + + fujitsu = kmalloc(sizeof(struct fujitsu_t), GFP_KERNEL); + if (!fujitsu) + return -ENOMEM; + memset(fujitsu, 0, sizeof(struct fujitsu_t)); + + result = acpi_bus_register_driver(&acpi_fujitsu_driver); + if (result < 0) { + ret = -ENODEV; + goto fail_acpi; + } + + /* Register backlight stuff */ + + fujitsu->bl_device = + backlight_device_register("fujitsu-laptop", NULL, NULL, + &fujitsubl_ops); + if (IS_ERR(fujitsu->bl_device)) + return PTR_ERR(fujitsu->bl_device); + + fujitsu->bl_device->props.max_brightness = FUJITSU_LCD_N_LEVELS - 1; + ret = platform_driver_register(&fujitsupf_driver); + if (ret) + goto fail_backlight; + + /* Register platform stuff */ + + fujitsu->pf_device = platform_device_alloc("fujitsu-laptop", -1); + if (!fujitsu->pf_device) { + ret = -ENOMEM; + goto fail_platform_driver; + } + + ret = platform_device_add(fujitsu->pf_device); + if (ret) + goto fail_platform_device1; + + ret = + sysfs_create_group(&fujitsu->pf_device->dev.kobj, + &fujitsupf_attribute_group); + if (ret) + goto fail_platform_device2; + + printk(KERN_INFO "fujitsu-laptop: driver " FUJITSU_DRIVER_VERSION + " successfully loaded.\n"); + + return 0; + + fail_platform_device2: + + platform_device_del(fujitsu->pf_device); + + fail_platform_device1: + + platform_device_put(fujitsu->pf_device); + + fail_platform_driver: + + platform_driver_unregister(&fujitsupf_driver); + + fail_backlight: + + backlight_device_unregister(fujitsu->bl_device); + + fail_acpi: + + kfree(fujitsu); + + return ret; +} + +static void __exit fujitsu_cleanup(void) +{ + sysfs_remove_group(&fujitsu->pf_device->dev.kobj, + &fujitsupf_attribute_group); + platform_device_unregister(fujitsu->pf_device); + platform_driver_unregister(&fujitsupf_driver); + backlight_device_unregister(fujitsu->bl_device); + + acpi_bus_unregister_driver(&acpi_fujitsu_driver); + + kfree(fujitsu); + + printk(KERN_INFO "fujitsu-laptop: driver unloaded.\n"); +} + +module_init(fujitsu_init); +module_exit(fujitsu_cleanup); + +MODULE_AUTHOR("Jonathan Woithe"); +MODULE_DESCRIPTION("Fujitsu laptop extras support"); +MODULE_VERSION(FUJITSU_DRIVER_VERSION); +MODULE_LICENSE("GPL");