Yeeloong is a laptop with a MIPS Loongson 2F processor, AMD CS5536
chipset, and KB3310B controller.

This yeeloong_laptop module enables access to sensors, battery,
video camera switch, external video connector event, and some
additional buttons.

This driver was orginally from linux-loongson-community. I Just do
some clean up and port to mainline kernel tree.

Signed-off-by: Jiaxun Yang <jiaxun.y...@flygoat.com>
---
 drivers/platform/mips/Kconfig           |   19 +
 drivers/platform/mips/Makefile          |    3 +
 drivers/platform/mips/yeeloong_laptop.c | 1142 +++++++++++++++++++++++++++++++
 3 files changed, 1164 insertions(+)
 create mode 100644 drivers/platform/mips/yeeloong_laptop.c

diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
index b3ae30a4c67b..acd27e36710b 100644
--- a/drivers/platform/mips/Kconfig
+++ b/drivers/platform/mips/Kconfig
@@ -23,4 +23,23 @@ config CPU_HWMON
        help
          Loongson-3A/3B CPU Hwmon (temperature sensor) driver.
 
+config LEMOTE_YEELOONG2F
+       tristate "Lemote YeeLoong Laptop"
+       depends on LEMOTE_MACH2F
+       select BACKLIGHT_LCD_SUPPORT
+       select LCD_CLASS_DEVICE
+       select BACKLIGHT_CLASS_DEVICE
+       select POWER_SUPPLY
+       select HWMON
+       select INPUT
+       select INPUT_MISC
+       select INPUT_SPARSEKMAP
+       select INPUT_EVDEV
+       default m
+       help
+         YeeLoong netbook is a mini laptop made by Lemote, which is basically
+         compatible to FuLoong2F mini PC, but it has an extra Embedded
+         Controller(kb3310b) for battery, hotkey, backlight, temperature and
+         fan management.
+
 endif # MIPS_PLATFORM_DEVICES
diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile
index 8dfd03924c37..b3172b081a2f 100644
--- a/drivers/platform/mips/Makefile
+++ b/drivers/platform/mips/Makefile
@@ -1 +1,4 @@
 obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o
+
+obj-$(CONFIG_LEMOTE_YEELOONG2F)        += yeeloong_laptop.o
+CFLAGS_yeeloong_laptop.o = -I$(srctree)/arch/mips/loongson/lemote-2f
diff --git a/drivers/platform/mips/yeeloong_laptop.c 
b/drivers/platform/mips/yeeloong_laptop.c
new file mode 100644
index 000000000000..61353c3e894c
--- /dev/null
+++ b/drivers/platform/mips/yeeloong_laptop.c
@@ -0,0 +1,1142 @@
+/*
+ * Driver for YeeLoong laptop extras
+ *
+ *  Copyright (C) 2017 Jiaxun Yang.
+ *  Author: Jiaxun Yang <jiaxun.y...@flygoat.com>
+ *
+ *  Copyright (C) 2009 Lemote Inc.
+ *  Author: Wu Zhangjin <wuzhang...@gmail.com>, Liu Junliang <li...@lemote.com>
+ *
+ *  Fixes: Petr Pisar <petr.pi...@atlas.cz>, 2012, 2013, 2014, 2015.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>   /* for backlight subdriver */
+#include <linux/fb.h>
+#include <linux/hwmon.h>       /* for hwmon subdriver */
+#include <linux/hwmon-sysfs.h>
+#include <linux/kernel.h>   /* for clamp_val() */
+#include <linux/input.h>       /* for hotkey subdriver */
+#include <linux/input/sparse-keymap.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>        /* for AC & Battery subdriver */
+#include <linux/module.h>   /* For MODULE_DEVICE_TABLE() */
+
+#include <asm/bootinfo.h>
+
+#include <cs5536/cs5536.h>
+
+#include <loongson.h>          /* for loongson_cmdline */
+#include <ec_kb3310b.h>
+
+/* common function */
+#define EC_VER_LEN 64
+
+static int ec_version_before(char *version)
+{
+       char *p, ec_ver[EC_VER_LEN];
+
+       p = strstr(loongson_cmdline, "EC_VER=");
+       if (!p)
+               memset(ec_ver, 0, EC_VER_LEN);
+       else {
+               strncpy(ec_ver, p, EC_VER_LEN);
+               p = strstr(ec_ver, " ");
+               if (p)
+                       *p = '\0';
+       }
+
+       return (strncasecmp(ec_ver, version, 64) < 0);
+}
+
+/* backlight subdriver */
+#define MAX_BRIGHTNESS 8
+
+static int yeeloong_set_brightness(struct backlight_device *bd)
+{
+       unsigned int level, current_level;
+       static unsigned int old_level;
+
+       level = (bd->props.fb_blank == FB_BLANK_UNBLANK &&
+                bd->props.power == FB_BLANK_UNBLANK) ?
+           bd->props.brightness : 0;
+
+       level = clamp_val(level, 0, MAX_BRIGHTNESS);
+
+       /* Avoid to modify the brightness when EC is tuning it */
+       if (old_level != level) {
+               current_level = ec_read(REG_DISPLAY_BRIGHTNESS);
+               if (old_level == current_level)
+                       ec_write(REG_DISPLAY_BRIGHTNESS, level);
+               old_level = level;
+       }
+
+       return 0;
+}
+
+static int yeeloong_get_brightness(struct backlight_device *bd)
+{
+       return ec_read(REG_DISPLAY_BRIGHTNESS);
+}
+
+const struct backlight_ops backlight_ops = {
+       .get_brightness = yeeloong_get_brightness,
+       .update_status = yeeloong_set_brightness,
+};
+
+static struct backlight_device *yeeloong_backlight_dev;
+
+static int yeeloong_backlight_init(void)
+{
+       int ret;
+       struct backlight_properties props;
+
+       memset(&props, 0, sizeof(struct backlight_properties));
+       props.type = BACKLIGHT_RAW;
+       props.max_brightness = MAX_BRIGHTNESS;
+       yeeloong_backlight_dev = backlight_device_register("backlight0", NULL,
+                       NULL, &backlight_ops, &props);
+
+       if (IS_ERR(yeeloong_backlight_dev)) {
+               ret = PTR_ERR(yeeloong_backlight_dev);
+               yeeloong_backlight_dev = NULL;
+               return ret;
+       }
+
+       yeeloong_backlight_dev->props.brightness =
+               yeeloong_get_brightness(yeeloong_backlight_dev);
+       backlight_update_status(yeeloong_backlight_dev);
+
+       return 0;
+}
+
+static void yeeloong_backlight_exit(void)
+{
+       if (yeeloong_backlight_dev) {
+               backlight_device_unregister(yeeloong_backlight_dev);
+               yeeloong_backlight_dev = NULL;
+       }
+}
+
+/* AC & Battery subdriver */
+
+static struct power_supply *yeeloong_ac, *yeeloong_bat;
+
+#define RET (val->intval)
+
+static inline bool is_ac_in(void)
+{
+       return !!(ec_read(REG_BAT_POWER) & BIT_BAT_POWER_ACIN);
+}
+
+static int yeeloong_get_ac_props(struct power_supply *psy,
+                               enum power_supply_property psp,
+                               union power_supply_propval *val)
+{
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               RET = is_ac_in();
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static enum power_supply_property yeeloong_ac_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc yeeloong_ac_desc = {
+       .name = "yeeloong-ac",
+       .type = POWER_SUPPLY_TYPE_MAINS,
+       .properties = yeeloong_ac_props,
+       .num_properties = ARRAY_SIZE(yeeloong_ac_props),
+       .get_property = yeeloong_get_ac_props,
+};
+
+#define BAT_CAP_CRITICAL 5
+#define BAT_CAP_HIGH     95
+
+#define get_bat_info(type) \
+       ((ec_read(REG_BAT_##type##_HIGH) << 8) | \
+        (ec_read(REG_BAT_##type##_LOW)))
+
+static inline bool is_bat_in(void)
+{
+       return !!(ec_read(REG_BAT_STATUS) & BIT_BAT_STATUS_IN);
+}
+
+static inline int get_bat_status(void)
+{
+       return ec_read(REG_BAT_STATUS);
+}
+
+static int get_battery_temp(void)
+{
+       int value;
+
+       value = get_bat_info(TEMPERATURE);
+
+       return value * 1000;
+}
+
+static int get_battery_current(void)
+{
+       s16 value;
+
+       value = get_bat_info(CURRENT);
+
+       return -value;
+}
+
+static int get_battery_voltage(void)
+{
+       int value;
+
+       value = get_bat_info(VOLTAGE);
+
+       return value;
+}
+
+static inline char *get_manufacturer(void)
+{
+       return (ec_read(REG_BAT_VENDOR) == FLAG_BAT_VENDOR_SANYO) ? "SANYO" :
+               "SIMPLO";
+}
+
+static int yeeloong_get_bat_props(struct power_supply *psy,
+                                    enum power_supply_property psp,
+                                    union power_supply_propval *val)
+{
+       switch (psp) {
+       /* Fixed information */
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+               /* mV -> µV */
+               RET = get_bat_info(DESIGN_VOL) * 1000;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+               /* mAh->µAh */
+               RET = get_bat_info(DESIGN_CAP) * 1000;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               /* µAh */
+               RET = get_bat_info(FULLCHG_CAP) * 1000;
+               break;
+       case POWER_SUPPLY_PROP_MANUFACTURER:
+               val->strval = get_manufacturer();
+               break;
+       /* Dynamic information */
+       case POWER_SUPPLY_PROP_PRESENT:
+               RET = is_bat_in();
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               /* mA -> µA */
+               RET = is_bat_in() ? get_battery_current() * 1000 : 0;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               /* mV -> µV */
+               RET = is_bat_in() ? get_battery_voltage() * 1000 : 0;
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               /* Celcius */
+               RET = is_bat_in() ? get_battery_temp() : 0;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               RET = is_bat_in() ? get_bat_info(RELATIVE_CAP) : 0;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+               {
+               int status;
+
+               if (!is_bat_in()) {
+                       RET = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+                       break;
+               }
+
+               status = get_bat_status();
+               RET = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+
+               if (unlikely(status & BIT_BAT_STATUS_DESTROY)) {
+                       RET = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+                       break;
+               }
+
+               if (status & BIT_BAT_STATUS_LOW)
+                       RET = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+               else if (status & BIT_BAT_STATUS_FULL)
+                       RET = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+               else {
+                       int curr_cap;
+
+                       curr_cap = get_bat_info(RELATIVE_CAP);
+
+                       if (curr_cap >= BAT_CAP_HIGH)
+                               RET = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+                       else if (curr_cap <= BAT_CAP_CRITICAL)
+                               RET = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+               }
+
+               } break;
+       case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+               /* seconds */
+               RET = is_bat_in() ?
+                       (get_bat_info(RELATIVE_CAP) - 3) * 54 + 142
+                       : 0;
+               break;
+       case POWER_SUPPLY_PROP_STATUS:
+               {
+                       int charge = ec_read(REG_BAT_CHARGE);
+
+                       if (charge & FLAG_BAT_CHARGE_DISCHARGE)
+                               RET = POWER_SUPPLY_STATUS_DISCHARGING;
+                       else if (charge & FLAG_BAT_CHARGE_CHARGE)
+                               RET = POWER_SUPPLY_STATUS_CHARGING;
+                       else
+                               RET = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               }
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               {
+                       int status;
+
+                       if (!is_bat_in()) {
+                               RET = POWER_SUPPLY_HEALTH_UNKNOWN;
+                               break;
+                       }
+
+                       status = get_bat_status();
+
+                       RET = POWER_SUPPLY_HEALTH_GOOD;
+                       if (status & (BIT_BAT_STATUS_DESTROY |
+                                               BIT_BAT_STATUS_LOW))
+                               RET = POWER_SUPPLY_HEALTH_DEAD;
+                       if (ec_read(REG_BAT_CHARGE_STATUS) &
+                                       BIT_BAT_CHARGE_STATUS_OVERTEMP)
+                               RET = POWER_SUPPLY_HEALTH_OVERHEAT;
+               }
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_NOW:      /* 1/100(%)*1000 µAh */
+               RET = get_bat_info(RELATIVE_CAP) *
+                       get_bat_info(FULLCHG_CAP) * 10;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+#undef RET
+
+static enum power_supply_property yeeloong_bat_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+       POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+       POWER_SUPPLY_PROP_CHARGE_FULL,
+       POWER_SUPPLY_PROP_CHARGE_NOW,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+       POWER_SUPPLY_PROP_TEMP,
+       POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const struct power_supply_desc yeeloong_bat_desc = {
+       .name = "yeeloongbattery",
+       .type = POWER_SUPPLY_TYPE_BATTERY,
+       .properties = yeeloong_bat_props,
+       .num_properties = ARRAY_SIZE(yeeloong_bat_props),
+       .get_property = yeeloong_get_bat_props,
+};
+
+static int ac_bat_initialized;
+
+static int yeeloong_bat_init(void)
+{
+       yeeloong_ac = power_supply_register(NULL, &yeeloong_ac_desc, NULL);
+       if (IS_ERR(yeeloong_ac))
+               return PTR_ERR(yeeloong_ac);
+       yeeloong_bat = power_supply_register(NULL, &yeeloong_bat_desc, NULL);
+       if (IS_ERR(yeeloong_bat)) {
+               power_supply_unregister(yeeloong_ac);
+               return PTR_ERR(yeeloong_bat);
+       }
+       ac_bat_initialized = 1;
+
+       return 0;
+}
+
+static void yeeloong_bat_exit(void)
+{
+       if (ac_bat_initialized) {
+               ac_bat_initialized = 0;
+
+               power_supply_unregister(yeeloong_ac);
+               power_supply_unregister(yeeloong_bat);
+       }
+}
+/* hwmon subdriver */
+
+#define MIN_FAN_SPEED 0
+#define MAX_FAN_SPEED 3
+
+static int get_fan_pwm_enable(void)
+{
+       int level, mode;
+
+       level = ec_read(REG_FAN_SPEED_LEVEL);
+       mode = ec_read(REG_FAN_AUTO_MAN_SWITCH);
+
+       if (level == MAX_FAN_SPEED && mode == BIT_FAN_MANUAL)
+               mode = 0;
+       else if (mode == BIT_FAN_MANUAL)
+               mode = 1;
+       else
+               mode = 2;
+
+       return mode;
+}
+
+static void set_fan_pwm_enable(int mode)
+{
+       switch (mode) {
+       case 0:
+               /* fullspeed */
+               ec_write(REG_FAN_AUTO_MAN_SWITCH, BIT_FAN_MANUAL);
+               ec_write(REG_FAN_SPEED_LEVEL, MAX_FAN_SPEED);
+               break;
+       case 1:
+               ec_write(REG_FAN_AUTO_MAN_SWITCH, BIT_FAN_MANUAL);
+               break;
+       case 2:
+               ec_write(REG_FAN_AUTO_MAN_SWITCH, BIT_FAN_AUTO);
+               break;
+       default:
+               break;
+       }
+}
+
+static int get_fan_pwm(void)
+{
+       return ec_read(REG_FAN_SPEED_LEVEL);
+}
+
+static void set_fan_pwm(int value)
+{
+       int mode;
+
+       mode = ec_read(REG_FAN_AUTO_MAN_SWITCH);
+       if (mode != BIT_FAN_MANUAL)
+               return;
+
+       value = clamp_val(value, 0, 3);
+
+       /* We must ensure the fan is on */
+       if (value > 0)
+               ec_write(REG_FAN_CONTROL, ON);
+
+       ec_write(REG_FAN_SPEED_LEVEL, value);
+}
+
+static int get_fan_rpm(void)
+{
+       int value;
+
+       value = FAN_SPEED_DIVIDER /
+           (((ec_read(REG_FAN_SPEED_HIGH) & 0x0f) << 8) |
+            ec_read(REG_FAN_SPEED_LOW));
+
+       return value;
+}
+
+static int get_cpu_temp(void)
+{
+       s8 value;
+
+       value = ec_read(REG_TEMPERATURE_VALUE);
+
+       return value * 1000;
+}
+
+static int get_cpu_temp_max(void)
+{
+       return 60 * 1000;
+}
+
+static int get_battery_temp_alarm(void)
+{
+       int status;
+
+       status = (ec_read(REG_BAT_CHARGE_STATUS) &
+                       BIT_BAT_CHARGE_STATUS_OVERTEMP);
+
+       return !!status;
+}
+
+static ssize_t store_sys_hwmon(void (*set) (int), const char *buf, size_t 
count)
+{
+       int ret;
+       unsigned long value;
+
+       if (!count)
+               return 0;
+
+       ret = kstrtoul(buf, 10, &value);
+       if (ret)
+               return ret;
+
+       set(value);
+
+       return count;
+}
+
+static ssize_t show_sys_hwmon(int (*get) (void), char *buf)
+{
+       return sprintf(buf, "%d\n", get());
+}
+
+#define CREATE_SENSOR_ATTR(_name, _mode, _set, _get)           \
+       static ssize_t show_##_name(struct device *dev,                 \
+                                   struct device_attribute *attr,      \
+                                   char *buf)                          \
+       {                                                               \
+               return show_sys_hwmon(_set, buf);                       \
+       }                                                               \
+       static ssize_t store_##_name(struct device *dev,                \
+                                    struct device_attribute *attr,     \
+                                    const char *buf, size_t count)     \
+       {                                                               \
+               return store_sys_hwmon(_get, buf, count);               \
+       }                                                               \
+       static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0)
+
+CREATE_SENSOR_ATTR(fan1_input, 0444, get_fan_rpm, NULL);
+CREATE_SENSOR_ATTR(pwm1, 0444 | 0644, get_fan_pwm, set_fan_pwm);
+CREATE_SENSOR_ATTR(pwm1_enable, 0444 | 0644, get_fan_pwm_enable,
+               set_fan_pwm_enable);
+CREATE_SENSOR_ATTR(temp1_input, 0444, get_cpu_temp, NULL);
+CREATE_SENSOR_ATTR(temp1_max, 0444, get_cpu_temp_max, NULL);
+CREATE_SENSOR_ATTR(temp2_input, 0444, get_battery_temp, NULL);
+CREATE_SENSOR_ATTR(temp2_max_alarm, 0444, get_battery_temp_alarm, NULL);
+CREATE_SENSOR_ATTR(curr1_input, 0444, get_battery_current, NULL);
+CREATE_SENSOR_ATTR(in1_input, 0444, get_battery_voltage, NULL);
+
+static ssize_t
+show_name(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "yeeloong\n");
+}
+
+static SENSOR_DEVICE_ATTR(name, 0444, show_name, NULL, 0);
+
+static struct attribute *hwmon_attributes[] = {
+       &sensor_dev_attr_pwm1.dev_attr.attr,
+       &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+       &sensor_dev_attr_fan1_input.dev_attr.attr,
+       &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_temp1_max.dev_attr.attr,
+       &sensor_dev_attr_temp2_input.dev_attr.attr,
+       &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+       &sensor_dev_attr_curr1_input.dev_attr.attr,
+       &sensor_dev_attr_in1_input.dev_attr.attr,
+       &sensor_dev_attr_name.dev_attr.attr,
+       NULL
+};
+
+static struct attribute_group hwmon_attribute_group = {
+       .attrs = hwmon_attributes
+};
+
+static struct device *yeeloong_hwmon_dev;
+
+static int yeeloong_hwmon_init(void)
+{
+       int ret;
+
+       yeeloong_hwmon_dev = hwmon_device_register(NULL);
+       if (IS_ERR(yeeloong_hwmon_dev)) {
+               pr_err("Fail to register yeeloong hwmon device\n");
+               yeeloong_hwmon_dev = NULL;
+               return PTR_ERR(yeeloong_hwmon_dev);
+       }
+       ret = sysfs_create_group(&yeeloong_hwmon_dev->kobj,
+                                &hwmon_attribute_group);
+       if (ret) {
+               hwmon_device_unregister(yeeloong_hwmon_dev);
+               yeeloong_hwmon_dev = NULL;
+               return ret;
+       }
+       /* ensure fan is set to auto mode */
+       set_fan_pwm_enable(2);
+
+       return 0;
+}
+
+static void yeeloong_hwmon_exit(void)
+{
+       if (yeeloong_hwmon_dev) {
+               sysfs_remove_group(&yeeloong_hwmon_dev->kobj,
+                                  &hwmon_attribute_group);
+               hwmon_device_unregister(yeeloong_hwmon_dev);
+               yeeloong_hwmon_dev = NULL;
+       }
+}
+
+/* video output controller */
+
+
+#define LCD    0
+#define CRT    1
+
+static void display_vo_set(int display, int on)
+{
+       int addr;
+       unsigned long value;
+
+       addr = (display == LCD) ? 0x31 : 0x21;
+
+       outb(addr, 0x3c4);
+       value = inb(0x3c5);
+
+       if (display == LCD)
+               value |= (on ? 0x03 : 0x02);
+       else {
+               if (on)
+                       clear_bit(7, &value);
+               else
+                       set_bit(7, &value);
+       }
+
+       outb(addr, 0x3c4);
+       outb(value, 0x3c5);
+}
+
+
+
+/* hotkey subdriver */
+
+static struct input_dev *yeeloong_hotkey_dev;
+
+static const struct key_entry yeeloong_keymap[] = {
+       {KE_SW, EVENT_LID, {SW_LID} },
+       /* Fn + ESC */
+       {KE_KEY, EVENT_CAMERA, {KEY_CAMERA} },
+       /* Fn + F1 */
+       {KE_KEY, EVENT_SLEEP, {KEY_SLEEP} },
+       /* Fn + F2 */
+       {KE_KEY, EVENT_DISPLAYTOGGLE, {KEY_DISPLAYTOGGLE} },
+       /* Fn + F3 */
+       {KE_KEY, EVENT_SWITCHVIDEOMODE, {KEY_SWITCHVIDEOMODE} },
+       /* Fn + F4 */
+       {KE_KEY, EVENT_AUDIO_MUTE, {KEY_MUTE} },
+       /* Fn + F5 */
+       {KE_KEY, EVENT_WLAN, {KEY_WLAN} },
+       /* Fn + up */
+       {KE_KEY, EVENT_DISPLAY_BRIGHTNESS, {KEY_BRIGHTNESSUP} },
+       /* Fn + down */
+       {KE_KEY, EVENT_DISPLAY_BRIGHTNESS, {KEY_BRIGHTNESSDOWN} },
+       /* Fn + right */
+       {KE_KEY, EVENT_AUDIO_VOLUME, {KEY_VOLUMEUP} },
+       /* Fn + left */
+       {KE_KEY, EVENT_AUDIO_VOLUME, {KEY_VOLUMEDOWN} },
+       {KE_END, 0} };
+
+static struct key_entry *get_event_key_entry(int event, int status)
+{
+       struct key_entry *ke;
+       static int old_brightness_status = -1;
+       static int old_volume_status = -1;
+
+       ke = sparse_keymap_entry_from_scancode(yeeloong_hotkey_dev, event);
+       if (!ke)
+               return NULL;
+
+       switch (event) {
+       case EVENT_DISPLAY_BRIGHTNESS:
+               /* current status > old one, means up */
+               if ((status == 0) || (status < old_brightness_status))
+                       ke++;
+               old_brightness_status = status;
+               break;
+       case EVENT_AUDIO_VOLUME:
+               if ((status == 0) || (status < old_volume_status))
+                       ke++;
+               old_volume_status = status;
+               break;
+       default:
+               break;
+       }
+
+       return ke;
+}
+
+static int report_lid_switch(int status)
+{
+       input_report_switch(yeeloong_hotkey_dev, SW_LID, !status);
+       input_sync(yeeloong_hotkey_dev);
+
+       return status;
+}
+
+static void yeeloong_vo_set(int lcd_status, int crt_status)
+{
+       display_vo_set(LCD, lcd_status);
+       display_vo_set(CRT, crt_status);
+}
+
+static int crt_detect_handler(int status)
+{
+       if (status)
+               yeeloong_vo_set(ON, ON);
+       else
+               yeeloong_vo_set(ON, OFF);
+
+       return status;
+}
+
+static int displaytoggle_handler(int status)
+{
+       /* EC(>=PQ1D26) does this job for us, we can not do it again,
+        * otherwise, the brightness will not resume to the normal level!
+        */
+       if (ec_version_before("EC_VER=PQ1D26"))
+               display_vo_set(LCD, status);
+
+       return status;
+}
+
+static int switchvideomode_handler(int status)
+{
+       static int video_output_status;
+
+       /* Only enable switch video output button
+        * when CRT is connected
+        */
+       if (ec_read(REG_CRT_DETECT) == OFF)
+               return 0;
+       /* 0. no CRT connected: LCD on, CRT off
+        * 1. BOTH on
+        * 2. LCD off, CRT on
+        * 3. BOTH off
+        * 4. LCD on, CRT off
+        */
+       video_output_status++;
+       if (video_output_status > 4)
+               video_output_status = 1;
+
+       switch (video_output_status) {
+       case 1:
+               yeeloong_vo_set(ON, ON);
+               break;
+       case 2:
+               yeeloong_vo_set(OFF, ON);
+               break;
+       case 3:
+               yeeloong_vo_set(OFF, OFF);
+               break;
+       case 4:
+               yeeloong_vo_set(ON, OFF);
+               break;
+       default:
+               /* Ensure LCD is on */
+               display_vo_set(LCD, ON);
+               break;
+       }
+       return video_output_status;
+}
+
+static int camera_handler(int status)
+{
+       int value;
+
+       value = ec_read(REG_CAMERA_CONTROL);
+       ec_write(REG_CAMERA_CONTROL, value | (1 << 1));
+
+       return status;
+}
+
+static int usb2_handler(int status)
+{
+       pr_emerg("USB2 Over Current occurred\n");
+
+       return status;
+}
+
+static int usb0_handler(int status)
+{
+       pr_emerg("USB0 Over Current occurred\n");
+
+       return status;
+}
+
+static int ac_bat_handler(int status)
+{
+       if (ac_bat_initialized) {
+               power_supply_changed(yeeloong_ac);
+               power_supply_changed(yeeloong_bat);
+       }
+       return status;
+}
+
+static void do_event_action(int event)
+{
+       sci_handler handler;
+       int reg, status;
+       struct key_entry *ke;
+
+       reg = 0;
+       handler = NULL;
+       status = 0;
+
+       switch (event) {
+       case EVENT_LID:
+               reg = REG_LID_DETECT;
+               break;
+       case EVENT_SWITCHVIDEOMODE:
+               handler = switchvideomode_handler;
+               break;
+       case EVENT_CRT_DETECT:
+               reg = REG_CRT_DETECT;
+               handler = crt_detect_handler;
+               break;
+       case EVENT_CAMERA:
+               reg = REG_CAMERA_STATUS;
+               handler = camera_handler;
+               break;
+       case EVENT_USB_OC2:
+               reg = REG_USB2_FLAG;
+               handler = usb2_handler;
+               break;
+       case EVENT_USB_OC0:
+               reg = REG_USB0_FLAG;
+               handler = usb0_handler;
+               break;
+       case EVENT_DISPLAYTOGGLE:
+               reg = REG_DISPLAY_LCD;
+               handler = displaytoggle_handler;
+               break;
+       case EVENT_AUDIO_MUTE:
+               reg = REG_AUDIO_MUTE;
+               break;
+       case EVENT_DISPLAY_BRIGHTNESS:
+               reg = REG_DISPLAY_BRIGHTNESS;
+               break;
+       case EVENT_AUDIO_VOLUME:
+               reg = REG_AUDIO_VOLUME;
+               break;
+       case EVENT_AC_BAT:
+               handler = ac_bat_handler;
+               break;
+       default:
+               break;
+       }
+
+       if (reg != 0)
+               status = ec_read(reg);
+
+       if (handler != NULL)
+               status = handler(status);
+
+       pr_debug("%s: event: %d status: %d\n", __func__, event, status);
+
+       /* Report current key to user-space */
+       ke = get_event_key_entry(event, status);
+       if (ke) {
+               if (ke->keycode == SW_LID)
+                       report_lid_switch(status);
+               else
+                       sparse_keymap_report_entry(yeeloong_hotkey_dev, ke, 1,
+                                       true);
+       }
+}
+
+/*
+ * SCI(system control interrupt) main interrupt routine
+ *
+ * We will do the query and get event number together so the interrupt routine
+ * should be longer than 120us now at least 3ms elpase for it.
+ */
+static irqreturn_t sci_irq_handler(int irq, void *dev_id)
+{
+       int ret, event;
+
+       if (irq != SCI_IRQ_NUM)
+               return IRQ_NONE;
+
+       /* Query the event number */
+       ret = ec_query_event_num();
+       if (ret < 0)
+               return IRQ_NONE;
+
+       event = ec_get_event_num();
+       if (event < EVENT_START || event > EVENT_END)
+               return IRQ_NONE;
+
+       /* Execute corresponding actions */
+       do_event_action(event);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * Config and init some msr and gpio register properly.
+ */
+static int sci_irq_init(void)
+{
+       u32 hi, lo;
+       u32 gpio_base;
+       unsigned long flags;
+       int ret;
+
+       /* Get gpio base */
+       _rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &lo);
+       gpio_base = lo & 0xff00;
+
+       /* Filter the former kb3310 interrupt for security */
+       ret = ec_query_event_num();
+       if (ret)
+               return ret;
+
+       /* For filtering next number interrupt */
+       mdelay(10000);
+
+       /* Set gpio native registers and msrs for GPIO27 SCI EVENT PIN
+        * gpio :
+        *      input, pull-up, no-invert, event-count and value 0,
+        *      no-filter, no edge mode
+        *      gpio27 map to Virtual gpio0
+        * msr :
+        *      no primary and lpc
+        *      Unrestricted Z input to IG10 from Virtual gpio 0.
+        */
+       local_irq_save(flags);
+       _rdmsr(0x80000024, &hi, &lo);
+       lo &= ~(1 << 10);
+       _wrmsr(0x80000024, hi, lo);
+       _rdmsr(0x80000025, &hi, &lo);
+       lo &= ~(1 << 10);
+       _wrmsr(0x80000025, hi, lo);
+       _rdmsr(0x80000023, &hi, &lo);
+       lo |= (0x0a << 0);
+       _wrmsr(0x80000023, hi, lo);
+       local_irq_restore(flags);
+
+       /* Set gpio27 as sci interrupt
+        *
+        * input, pull-up, no-fliter, no-negedge, invert
+        * the sci event is just about 120us
+        */
+
+       /*  input enable */
+       outl(0x00000800, (gpio_base | 0xA0));
+       /*  revert the input */
+       outl(0x00000800, (gpio_base | 0xA4));
+       /*  event-int enable */
+       outl(0x00000800, (gpio_base | 0xB8));
+
+       return 0;
+}
+
+static int yeeloong_hotkey_init(void)
+{
+       int ret;
+
+       ret = sci_irq_init();
+       if (ret)
+               return -EFAULT;
+
+       ret = request_threaded_irq(SCI_IRQ_NUM, NULL, &sci_irq_handler,
+                       IRQF_ONESHOT, "sci", NULL);
+       if (ret)
+               return -EFAULT;
+
+       yeeloong_hotkey_dev = input_allocate_device();
+
+       if (!yeeloong_hotkey_dev) {
+               free_irq(SCI_IRQ_NUM, NULL);
+               return -ENOMEM;
+       }
+
+       yeeloong_hotkey_dev->name = "HotKeys";
+       yeeloong_hotkey_dev->phys = "button/input0";
+       yeeloong_hotkey_dev->id.bustype = BUS_HOST;
+       yeeloong_hotkey_dev->dev.parent = NULL;
+
+       ret = sparse_keymap_setup(yeeloong_hotkey_dev, yeeloong_keymap, NULL);
+       if (ret) {
+               pr_err("Fail to setup input device keymap\n");
+               input_free_device(yeeloong_hotkey_dev);
+               return ret;
+       }
+
+       ret = input_register_device(yeeloong_hotkey_dev);
+       if (ret) {
+               input_free_device(yeeloong_hotkey_dev);
+               return ret;
+       }
+
+       /* Update the current status of LID */
+       report_lid_switch(ON);
+
+#ifdef CONFIG_LOONGSON_SUSPEND
+       /* Install the real yeeloong_report_lid_status for pm.c */
+       yeeloong_report_lid_status = report_lid_switch;
+#endif
+
+       return 0;
+}
+
+static void yeeloong_hotkey_exit(void)
+{
+       /* Free irq */
+       free_irq(SCI_IRQ_NUM, NULL);
+
+#ifdef CONFIG_LOONGSON_SUSPEND
+       /* Uninstall yeeloong_report_lid_status for pm.c */
+       if (yeeloong_report_lid_status == report_lid_switch)
+               yeeloong_report_lid_status = NULL;
+#endif
+
+       if (yeeloong_hotkey_dev) {
+               input_unregister_device(yeeloong_hotkey_dev);
+               yeeloong_hotkey_dev = NULL;
+       }
+}
+
+#ifdef CONFIG_PM
+static void usb_ports_set(int status)
+{
+       status = !!status;
+
+       ec_write(REG_USB0_FLAG, status);
+       ec_write(REG_USB1_FLAG, status);
+       ec_write(REG_USB2_FLAG, status);
+}
+
+static int yeeloong_suspend(struct device *dev)
+
+{
+       if (ec_version_before("EC_VER=PQ1D27"))
+               display_vo_set(LCD, OFF);
+       display_vo_set(CRT, OFF);
+       usb_ports_set(OFF);
+
+       return 0;
+}
+
+static int yeeloong_resume(struct device *dev)
+{
+       int ret;
+
+       if (ec_version_before("EC_VER=PQ1D27"))
+               display_vo_set(LCD, ON);
+       display_vo_set(CRT, ON);
+       usb_ports_set(ON);
+
+       ret = sci_irq_init();
+       if (ret)
+               return -EFAULT;
+
+       return 0;
+}
+
+static const SIMPLE_DEV_PM_OPS(yeeloong_pm_ops, yeeloong_suspend,
+       yeeloong_resume);
+#endif
+
+static struct platform_device_id platform_device_ids[] = {
+       {
+               .name = "yeeloong_laptop",
+       },
+       {}
+};
+
+MODULE_DEVICE_TABLE(platform, platform_device_ids);
+
+static struct platform_driver platform_driver = {
+       .driver = {
+               .name = "yeeloong_laptop",
+               .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+               .pm = &yeeloong_pm_ops,
+#endif
+       },
+       .id_table = platform_device_ids,
+};
+
+static int __init yeeloong_init(void)
+{
+       int ret;
+
+       if (mips_machtype != MACH_LEMOTE_YL2F89) {
+               pr_err("Unsupported system.\n");
+               return -ENODEV;
+       }
+
+       pr_info("Load YeeLoong Laptop Platform Specific Driver.\n");
+
+       /* Register platform stuff */
+       ret = platform_driver_register(&platform_driver);
+       if (ret) {
+               pr_err("Fail to register yeeloong platform driver.\n");
+               return ret;
+       }
+
+       ret = yeeloong_backlight_init();
+       if (ret) {
+               pr_err("Fail to register yeeloong backlight driver.\n");
+               yeeloong_backlight_exit();
+               return ret;
+       }
+
+       ret = yeeloong_bat_init();
+       if (ret) {
+               pr_err("Fail to register yeeloong battery driver.\n");
+               yeeloong_bat_exit();
+               return ret;
+       }
+
+       ret = yeeloong_hwmon_init();
+       if (ret) {
+               pr_err("Fail to register yeeloong hwmon driver.\n");
+               yeeloong_hwmon_exit();
+               return ret;
+       }
+
+       ret = yeeloong_hotkey_init();
+       if (ret) {
+               pr_err("Fail to register yeeloong hotkey driver.\n");
+               yeeloong_hotkey_exit();
+               return ret;
+       }
+
+       return 0;
+}
+
+static void __exit yeeloong_exit(void)
+{
+       yeeloong_hotkey_exit();
+       yeeloong_hwmon_exit();
+       yeeloong_bat_exit();
+       yeeloong_backlight_exit();
+       platform_driver_unregister(&platform_driver);
+
+       pr_info("Unload YeeLoong Platform Specific Driver.\n");
+}
+
+module_init(yeeloong_init);
+module_exit(yeeloong_exit);
+
+MODULE_AUTHOR("Wu Zhangjin <wuzhang...@gmail.com>; Liu Junliang 
<li...@lemote.com>");
+MODULE_DESCRIPTION("YeeLoong laptop driver");
+MODULE_LICENSE("GPL");
-- 
2.15.0

Reply via email to