Received: by 10.223.164.202 with SMTP id h10csp237549wrb; Mon, 13 Nov 2017 17:33:43 -0800 (PST) X-Google-Smtp-Source: AGs4zMamiyj2EXy70zsAVChOcfFpo3DJmf9ntzMtPMF3QZvnp7yDoOGSv0xZciU5m5+5Bcfx1E7h X-Received: by 10.159.203.133 with SMTP id ay5mr10771976plb.12.1510623223751; Mon, 13 Nov 2017 17:33:43 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1510623223; cv=none; d=google.com; s=arc-20160816; b=0DqjTZr/B+Uy2sdQ8UVdLs1qDGG49bjfrmhGJjCligxIx0PxU5h0gVNP86Z4yv4sQ2 VVbEALCTLyD02C8KpyFsP49r8iQbVYOcAbddPEG/A1FG4coduE+rYGdelxQdUaAXCzsR 8LU+cKiTo+bhqoHOwsLMmH9j+y/B6RzxdW+GSXG+zFu6ZjxrfZqOA/FNvUx2pPzEXf3S 7DG60G5J+QnJ5Dd/as2fIUB1bAlNKCq1cHmxEeZCqNmXdTZS9RP4F/A/NH5YuS0nfbPJ +Jcngl+85Jq+MN7dumbqWkQVZvOF3WDZYfl0doZXGPGuOuWIbVocNYMuYwXtdCH0FxwR R2og== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature:arc-authentication-results; bh=YJO0rr8OwXBDZL0xYDbbyh9c+cMnNoTnMSngQBwGTu4=; b=PUgX80ZvJmdR2cwZcxO6pbzSUAYHyQR8UPTuOuBjovggkm+AFtAek/6rNtLQM8HvIQ 3rUXU/sOaNwfFB1bj1VOSXT1rUdC89l7IaIWxk2n0gDKk1xeKi88G0s4mQlNlU5yeXAF O21F5116IC9IYRoMzkTM8r0bKfGNlBjFvohch/CXCYyw1IFP+IpEoMphi6wUYQsG1dEc As4NQXCWMYb7i2xPOBUUTRfL/E8a8HVmNOXybFga71BbbMudGIKKUR3p0SFNnjmvGBgt BS186nL2HRTA9dMjD+XY8fXdo9jfKLBVAM63PEXyA3oWLaP5EBRLlFTh6TyBT55qG9Es vdFw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@flygoat.com header.s=mail header.b=VsbpSQMl; dkim=pass header.i=@flygoat.com header.s=mail header.b=vaMIcwtw; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id b15si2271942pll.79.2017.11.13.17.33.30; Mon, 13 Nov 2017 17:33:43 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@flygoat.com header.s=mail header.b=VsbpSQMl; dkim=pass header.i=@flygoat.com header.s=mail header.b=vaMIcwtw; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751438AbdKNBcx (ORCPT + 89 others); Mon, 13 Nov 2017 20:32:53 -0500 Received: from forward104o.mail.yandex.net ([37.140.190.179]:42622 "EHLO forward104o.mail.yandex.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750779AbdKNBcu (ORCPT ); Mon, 13 Nov 2017 20:32:50 -0500 X-Greylist: delayed 322 seconds by postgrey-1.27 at vger.kernel.org; Mon, 13 Nov 2017 20:32:49 EST Received: from mxback15g.mail.yandex.net (mxback15g.mail.yandex.net [IPv6:2a02:6b8:0:1472:2741:0:8b7:94]) by forward104o.mail.yandex.net (Yandex) with ESMTP id 6670C702EDD; Tue, 14 Nov 2017 04:27:44 +0300 (MSK) Received: from smtp4j.mail.yandex.net (smtp4j.mail.yandex.net [2a02:6b8:0:1619::15:6]) by mxback15g.mail.yandex.net (nwsmtp/Yandex) with ESMTP id ZMG7tqaLOC-RiKOWI7b; Tue, 14 Nov 2017 04:27:44 +0300 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flygoat.com; s=mail; t=1510622864; bh=YJO0rr8OwXBDZL0xYDbbyh9c+cMnNoTnMSngQBwGTu4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=VsbpSQMlrOfCYXOySJ2E/ETt35lOFKJzBmpzVmeICATStBYo7ghHOhihaTNPZ/4SR FpGArO0M+2BQLrm/Hy9GgXwqb9CtpXDE76CiLHLd5kQ/DWjJuFp/RA4o5QlSL5vmRX sBiKVxEJXB61vbPs5rVhpILiyWI6fnQtUKO2ZiH8= Received: by smtp4j.mail.yandex.net (nwsmtp/Yandex) with ESMTPSA id 7l823DlZ0a-Rch09rxX; Tue, 14 Nov 2017 04:27:41 +0300 (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (Client certificate not present) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flygoat.com; s=mail; t=1510622863; bh=YJO0rr8OwXBDZL0xYDbbyh9c+cMnNoTnMSngQBwGTu4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=vaMIcwtwg+JD/zflh0lAnC8HnfUNHrfvmib6Nl3B+GJM9MlrsZ6FrNN1h3scC0g0Y hNswLFo8R3zTtqELSlA9y1s+vqcKIRp7Yc0HifZPPY+B4CnOUdV9LnLhQwfsj71idE 53DEHx3dbgjMHfepH+Oz74xWuoSGg00rKkz5BqT0= Authentication-Results: smtp4j.mail.yandex.net; dkim=pass header.i=@flygoat.com From: Jiaxun Yang To: Ralf Baechle Cc: linux-mips@linux-mips.org, linux-kernel@vger.kernel.org, Jiaxun Yang Subject: [PATCH v2 3/4] MIPS: Loongson64: Yeeloong add platform driver Date: Tue, 14 Nov 2017 09:26:48 +0800 Message-Id: <20171114012649.29625-3-jiaxun.yang@flygoat.com> X-Mailer: git-send-email 2.14.1 In-Reply-To: <20171114012649.29625-1-jiaxun.yang@flygoat.com> References: <20171114012649.29625-1-jiaxun.yang@flygoat.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 --- drivers/platform/mips/Kconfig | 18 + drivers/platform/mips/Makefile | 3 + drivers/platform/mips/yeeloong_laptop.c | 1143 +++++++++++++++++++++++++++++++ 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..0f054efc26e3 100644 --- a/drivers/platform/mips/Kconfig +++ b/drivers/platform/mips/Kconfig @@ -23,4 +23,22 @@ 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_SPARSEKMAP + select INPUT_EVDEV + depends on INPUT + 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..280f2044858b --- /dev/null +++ b/drivers/platform/mips/yeeloong_laptop.c @@ -0,0 +1,1143 @@ +/* + * Driver for YeeLoong laptop extras + * + * Copyright (C) 2017 Jiaxun Yang. + * Author: Jiaxun Yang + * + * Copyright (C) 2009 Lemote Inc. + * Author: Wu Zhangjin , Liu Junliang + * + * Fixes: Petr Pisar , 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 +#include +#include /* for backlight subdriver */ +#include +#include /* for hwmon subdriver */ +#include +#include /* for clamp_val() */ +#include /* for hotkey subdriver */ +#include +#include +#include +#include /* for AC & Battery subdriver */ +#include /* For MODULE_DEVICE_TABLE() */ + +#include + +#include + +#include /* for loongson_cmdline */ +#include + +/* 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 = "yeeloong-bat", + .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 + */ + asm(".set noreorder\n"); + /* input enable */ + outl(0x00000800, (gpio_base | 0xA0)); + /* revert the input */ + outl(0x00000800, (gpio_base | 0xA4)); + /* event-int enable */ + outl(0x00000800, (gpio_base | 0xB8)); + asm(".set reorder\n"); + + 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 ; Liu Junliang "); +MODULE_DESCRIPTION("YeeLoong laptop driver"); +MODULE_LICENSE("GPL"); -- 2.14.1 From 1584006923897382466@xxx Tue Nov 14 02:32:00 +0000 2017 X-GM-THRID: 1583894279420660548 X-Gmail-Labels: Inbox,Category Forums,HistoricalUnread