From 9d62f1b8abb852a12a437b94ddc67c3294dd0953 Mon Sep 17 00:00:00 2001
From: Nick Xie <nick@khadas.com>
Date: Mon, 23 Dec 2019 22:51:19 +0800
Subject: [PATCH 098/101] arm64: dts: VIMs: add simple MCU driver for FAN

Signed-off-by: Nick Xie <nick@khadas.com>
---
 .../amlogic/meson-gxl-s905x-khadas-vim.dts    |  10 +
 .../dts/amlogic/meson-gxm-khadas-vim2.dts     |  10 +
 .../boot/dts/amlogic/meson-khadas-vim3.dtsi   |  10 +
 drivers/hwmon/scpi-hwmon.c                    |  18 +
 drivers/misc/Kconfig                          |   5 +
 drivers/misc/Makefile                         |   1 +
 drivers/misc/khadas-mcu.c                     | 720 ++++++++++++++++++
 drivers/thermal/amlogic_thermal.c             |  19 +
 8 files changed, 793 insertions(+)
 create mode 100644 drivers/misc/khadas-mcu.c

diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts
index 0786ea55f839..02b6691768b3 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts
@@ -155,6 +155,16 @@
 		clock-frequency = <32768>;
 		clock-output-names = "xin32k";
 	};
+
+	khadas-mcu@18 {
+		status = "okay";
+		compatible = "khadas-mcu";
+		reg = <0x18>;
+		fan,trig_temp_level0 = <50>;
+		fan,trig_temp_level1 = <60>;
+		fan,trig_temp_level2 = <70>;
+		hwver = "VIM1.V12"; /* Will be updated in uboot. */
+	};
 };
 
 &ir {
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts b/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts
index 57de06faa841..f9ec3f3efbe1 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts
@@ -363,6 +363,16 @@
 		clock-frequency = <32768>;
 		clock-output-names = "xin32k";
 	};
+
+	khadas-mcu@18 {
+		status = "okay";
+		compatible = "khadas-mcu";
+		reg = <0x18>;
+		fan,trig_temp_level0 = <50>;
+		fan,trig_temp_level1 = <60>;
+		fan,trig_temp_level2 = <70>;
+		hwver = "VIM2.V12"; /* Will be updated in uboot. */
+	};
 };
 
 &ir {
diff --git a/arch/arm64/boot/dts/amlogic/meson-khadas-vim3.dtsi b/arch/arm64/boot/dts/amlogic/meson-khadas-vim3.dtsi
index 6d0163f56b0d..f6b5de8328ac 100644
--- a/arch/arm64/boot/dts/amlogic/meson-khadas-vim3.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-khadas-vim3.dtsi
@@ -242,6 +242,16 @@
 		reg = <0x51>;
 		#clock-cells = <0>;
 	};
+
+	khadas-mcu@18 {
+		status = "okay";
+		compatible = "khadas-mcu";
+		reg = <0x18>;
+		fan,trig_temp_level0 = <50>;
+		fan,trig_temp_level1 = <60>;
+		fan,trig_temp_level2 = <70>;
+		hwver = "VIM3.V11"; /* Will be updated in uboot. */
+	};
 };
 
 &ir {
diff --git a/drivers/hwmon/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c
index 25aac40f2764..3dd7af04c5bd 100644
--- a/drivers/hwmon/scpi-hwmon.c
+++ b/drivers/hwmon/scpi-hwmon.c
@@ -54,6 +54,8 @@ static const u32 scpi_scale[] = {
 	[ENERGY]	= 1000000,	/* (microjoules)	*/
 };
 
+static struct scpi_thermal_zone *g_scpi_thermal_zone_ptr;
+
 static void scpi_scale_reading(u64 *value, struct sensor_data *sensor)
 {
 	if (scpi_scale[sensor->info.class] != sensor->scale) {
@@ -81,6 +83,20 @@ static int scpi_read_temp(void *dev, int *temp)
 	return 0;
 }
 
+int meson_gx_get_temperature(void)
+{
+	int temp;
+	int ret;
+	ret = scpi_read_temp(g_scpi_thermal_zone_ptr, &temp);
+	if (ret) {
+		printk("scpi_read_temp() failed!\n");
+		return ret;
+	}
+
+	return temp / 1000;
+}
+EXPORT_SYMBOL(meson_gx_get_temperature);
+
 /* hwmon callback functions */
 static ssize_t
 scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
@@ -266,6 +282,8 @@ static int scpi_hwmon_probe(struct platform_device *pdev)
 		if (!zone)
 			return -ENOMEM;
 
+		g_scpi_thermal_zone_ptr = zone;
+
 		zone->sensor_id = i;
 		zone->scpi_sensors = scpi_sensors;
 		z = devm_thermal_zone_of_sensor_register(dev,
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7f0d48f406e3..fb0a3830fd87 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -465,6 +465,11 @@ config PVPANIC
 	  a paravirtualized device provided by QEMU; it lets a virtual machine
 	  (guest) communicate panic events to the host.
 
+config KHADAS_MCU
+	tristate "Khadas boards on-board MCU"
+	help
+	  This driver provides support for Khadas boards on-board MCU.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c1860d35dc7e..9bbf2a479405 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -57,4 +57,5 @@ obj-y				+= cardreader/
 obj-$(CONFIG_HABANA_AI)		+= habanalabs/
 obj-$(CONFIG_UACCE)		+= uacce/
 obj-$(CONFIG_XILINX_SDFEC)	+= xilinx_sdfec.o
+obj-$(CONFIG_KHADAS_MCU) += khadas-mcu.o
 obj-$(CONFIG_HISI_HIKEY_USB)	+= hisi_hikey_usb.o
diff --git a/drivers/misc/khadas-mcu.c b/drivers/misc/khadas-mcu.c
new file mode 100644
index 000000000000..7c6f2903b7a5
--- /dev/null
+++ b/drivers/misc/khadas-mcu.c
@@ -0,0 +1,720 @@
+/*
+ * Khadas MCU control driver
+ *
+ * Written by: Nick <nick@khadas.com>
+ *
+ * Copyright (c) 2019 Shenzhen Wesion Technology Co., Ltd.
+ *
+ * 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/module.h>
+#include <linux/i2c.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/sysfs.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+
+/* Device registers */
+#define MCU_BOOT_EN_WOL_REG             0x21
+#define MCU_CMD_FAN_STATUS_CTRL_REG     0x88
+#define MCU_USB_PCIE_SWITCH_REG         0x33 /* VIM3/VIM3L only */
+#define MCU_PWR_OFF_CMD_REG             0x80
+#define MCU_SHUTDOWN_NORMAL_REG         0x2c
+
+#define MCU_FAN_TRIG_TEMP_LEVEL0        50	// 50 degree if not set
+#define MCU_FAN_TRIG_TEMP_LEVEL1        60	// 60 degree if not set
+#define MCU_FAN_TRIG_TEMP_LEVEL2        70	// 70 degree if not set
+#define MCU_FAN_TRIG_MAXTEMP            80
+#define MCU_FAN_LOOP_SECS               (30 * HZ)	// 30 seconds
+#define MCU_FAN_TEST_LOOP_SECS          (5 * HZ)  // 5 seconds
+#define MCU_FAN_LOOP_NODELAY_SECS       0
+#define MCU_FAN_SPEED_OFF               0
+#define MCU_FAN_SPEED_LOW               1
+#define MCU_FAN_SPEED_MID               2
+#define MCU_FAN_SPEED_HIGH              3
+
+enum mcu_fan_mode {
+	MCU_FAN_MODE_MANUAL = 0,
+	MCU_FAN_MODE_AUTO,
+};
+
+enum mcu_fan_level {
+	MCU_FAN_LEVEL_0 = 0,
+	MCU_FAN_LEVEL_1,
+	MCU_FAN_LEVEL_2,
+	MCU_FAN_LEVEL_3,
+};
+
+enum mcu_fan_status {
+	MCU_FAN_STATUS_DISABLE = 0,
+	MCU_FAN_STATUS_ENABLE,
+};
+
+enum mcu_usb_pcie_switch_mode {
+	MCU_USB_PCIE_SWITCH_MODE_USB3 = 0,
+	MCU_USB_PCIE_SWITCH_MODE_PCIE
+};
+
+enum khadas_board_hwver {
+	KHADAS_BOARD_HWVER_NONE = 0,
+	KHADAS_BOARD_HWVER_V11,
+	KHADAS_BOARD_HWVER_V12,
+	KHADAS_BOARD_HWVER_V13,
+	KHADAS_BOARD_HWVER_V14
+};
+
+enum khadas_board {
+	KHADAS_BOARD_NONE,
+	KHADAS_BOARD_VIM1,
+	KHADAS_BOARD_VIM2,
+	KHADAS_BOARD_VIM3
+};
+
+struct mcu_fan_data {
+	struct platform_device *pdev;
+	struct class *fan_class;
+	struct delayed_work work;
+	struct delayed_work fan_test_work;
+	enum    mcu_fan_status enable;
+	enum 	mcu_fan_mode mode;
+	enum 	mcu_fan_level level;
+	int	trig_temp_level0;
+	int	trig_temp_level1;
+	int	trig_temp_level2;
+};
+
+struct mcu_data {
+	struct i2c_client *client;
+	struct class *usb_pcie_switch_class;
+	struct class *mcu_class;
+	u8 usb_pcie_switch_mode;
+	enum khadas_board board;
+	enum khadas_board_hwver hwver;
+	struct mcu_fan_data fan_data;
+};
+
+struct mcu_data *g_mcu_data;
+
+static char * mcu_board_type_to_str(enum khadas_board board)
+{
+	switch (board) {
+		case KHADAS_BOARD_NONE:
+			return "Unknown";
+		case KHADAS_BOARD_VIM1:
+			return "VIM1";
+		case KHADAS_BOARD_VIM2:
+			return "VIM2";
+		case KHADAS_BOARD_VIM3:
+			return "VIM3";
+		default:
+			return "Unknown";
+	}
+}
+
+static char * mcu_board_hardware_version_str(enum khadas_board_hwver hwver)
+{
+	switch (hwver) {
+		case KHADAS_BOARD_HWVER_NONE:
+			return "Unknown";
+		case KHADAS_BOARD_HWVER_V11:
+			return "V11";
+		case KHADAS_BOARD_HWVER_V12:
+			return "V12";
+		case KHADAS_BOARD_HWVER_V13:
+			return "V13";
+		case KHADAS_BOARD_HWVER_V14:
+			return "V14";
+		default:
+			return "Unknown";
+	}
+}
+
+static int i2c_master_reg8_send(const struct i2c_client *client,
+		const char reg, const char *buf, int count)
+{
+	struct i2c_adapter *adap = client->adapter;
+	struct i2c_msg msg;
+	int ret;
+	char *tx_buf = kzalloc(count + 1, GFP_KERNEL);
+	if (!tx_buf)
+		return -ENOMEM;
+	tx_buf[0] = reg;
+	memcpy(tx_buf+1, buf, count);
+
+	msg.addr = client->addr;
+	msg.flags = client->flags;
+	msg.len = count + 1;
+	msg.buf = (char *)tx_buf;
+
+	ret = i2c_transfer(adap, &msg, 1);
+	kfree(tx_buf);
+	return (ret == 1) ? count : ret;
+}
+
+static int i2c_master_reg8_recv(const struct i2c_client *client,
+		const char reg, char *buf, int count)
+{
+	struct i2c_adapter *adap = client->adapter;
+	struct i2c_msg msgs[2];
+	int ret;
+	char reg_buf = reg;
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags;
+	msgs[0].len = 1;
+	msgs[0].buf = &reg_buf;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags | I2C_M_RD;
+	msgs[1].len = count;
+	msgs[1].buf = (char *)buf;
+
+	ret = i2c_transfer(adap, msgs, 2);
+
+	return (ret == 2) ? count : ret;
+}
+
+static int mcu_i2c_read_regs(struct i2c_client *client,
+		u8 reg, u8 buf[], unsigned len)
+{
+	int ret;
+	ret = i2c_master_reg8_recv(client, reg, buf, len);
+	return ret;
+}
+
+static int mcu_i2c_write_regs(struct i2c_client *client,
+		u8 reg, u8 const buf[], __u16 len)
+{
+	int ret;
+	ret = i2c_master_reg8_send(client, reg, buf, (int)len);
+	return ret;
+}
+
+static int is_mcu_fan_control_supported(void)
+{
+	// MCU FAN control is supported for:
+	// 1. Khadas VIM1 V13 and later
+	// 2. Khadas VIM2 V13 and later
+	// 3. Khadas VIM3 V11 and later
+	if (KHADAS_BOARD_VIM1 == g_mcu_data->board) {
+		if (g_mcu_data->hwver >= KHADAS_BOARD_HWVER_V13)
+			return 1;
+		else
+			return 0;
+	} else if (KHADAS_BOARD_VIM2 == g_mcu_data->board) {
+		if (g_mcu_data->hwver > KHADAS_BOARD_HWVER_V12)
+			return 1;
+		else
+			return 0;
+	} else if (KHADAS_BOARD_VIM3 == g_mcu_data->board) {
+		if (g_mcu_data->hwver >= KHADAS_BOARD_HWVER_V11)
+			return 1;
+		else
+			return 0;
+	} else
+		return 0;
+}
+
+static bool is_mcu_usb_pcie_switch_supported(void)
+{
+	// MCU USB PCIe switch is supported for:
+	// 1. Khadas VIM3
+	if (KHADAS_BOARD_VIM3 == g_mcu_data->board)
+		return 1;
+	else
+		return 0;
+}
+
+static void mcu_fan_level_set(struct mcu_fan_data *fan_data, int level)
+{
+	if (is_mcu_fan_control_supported()) {
+		int ret;
+		u8 data[2] = {0};
+
+		if(0 == level) {
+			data[0] = MCU_FAN_SPEED_OFF;
+		}else if(1 == level){
+			data[0] = MCU_FAN_SPEED_LOW;
+		}else if(2 == level){
+			data[0] = MCU_FAN_SPEED_MID;
+		}else if(3 == level){
+			data[0] = MCU_FAN_SPEED_HIGH;
+		}
+
+		g_mcu_data->fan_data.level = data[0];
+
+		ret = mcu_i2c_write_regs(g_mcu_data->client, MCU_CMD_FAN_STATUS_CTRL_REG, data, 1);
+		if (ret < 0) {
+			printk("write fan control err\n");
+			return;
+		}
+	}
+}
+
+extern int meson_gx_get_temperature(void);
+extern int meson_g12_get_temperature(void);
+static void fan_work_func(struct work_struct *_work)
+{
+	if (is_mcu_fan_control_supported()) {
+		int temp = -EINVAL;
+		struct mcu_fan_data *fan_data = &g_mcu_data->fan_data;
+
+		if ((KHADAS_BOARD_VIM1 == g_mcu_data->board) ||
+			(KHADAS_BOARD_VIM2 == g_mcu_data->board))
+			temp = meson_gx_get_temperature();
+		else if (KHADAS_BOARD_VIM3 == g_mcu_data->board)
+			temp = meson_g12_get_temperature();
+
+		if(temp != -EINVAL){
+			if(temp < fan_data->trig_temp_level0 ) {
+				mcu_fan_level_set(fan_data, 0);
+			}else if(temp < fan_data->trig_temp_level1 ) {
+				mcu_fan_level_set(fan_data, 1);
+			}else if(temp < fan_data->trig_temp_level2 ) {
+				mcu_fan_level_set(fan_data, 2);
+			}else{
+				mcu_fan_level_set(fan_data, 3);
+			}
+		}
+
+		schedule_delayed_work(&fan_data->work, MCU_FAN_LOOP_SECS);
+	}
+}
+
+static void khadas_fan_set(struct mcu_fan_data  *fan_data)
+{
+	if (is_mcu_fan_control_supported()) {
+
+		cancel_delayed_work(&fan_data->work);
+
+		if (fan_data->enable == MCU_FAN_STATUS_DISABLE) {
+			mcu_fan_level_set(fan_data, 0);
+			return;
+		}
+		switch (fan_data->mode) {
+			case MCU_FAN_MODE_MANUAL:
+				switch(fan_data->level) {
+					case MCU_FAN_LEVEL_0:
+						mcu_fan_level_set(fan_data, 0);
+						break;
+					case MCU_FAN_LEVEL_1:
+						mcu_fan_level_set(fan_data, 1);
+						break;
+					case MCU_FAN_LEVEL_2:
+						mcu_fan_level_set(fan_data, 2);
+						break;
+					case MCU_FAN_LEVEL_3:
+						mcu_fan_level_set(fan_data, 3);
+						break;
+					default:
+						break;
+				}
+				break;
+
+			case MCU_FAN_MODE_AUTO:
+				// FIXME: achieve with a better way
+				schedule_delayed_work(&fan_data->work, MCU_FAN_LOOP_NODELAY_SECS);
+				break;
+
+			default:
+				break;
+		}
+	}
+}
+
+static ssize_t show_fan_enable(struct class *cls,
+			 struct class_attribute *attr, char *buf)
+{
+	return sprintf(buf, "Fan enable: %d\n", g_mcu_data->fan_data.enable);
+}
+
+static ssize_t store_fan_enable(struct class *cls, struct class_attribute *attr,
+		       const char *buf, size_t count)
+{
+	int enable;
+
+	if (kstrtoint(buf, 0, &enable))
+		return -EINVAL;
+
+	// 0: manual, 1: auto
+	if( enable >= 0 && enable < 2 ){
+		g_mcu_data->fan_data.enable = enable;
+		khadas_fan_set(&g_mcu_data->fan_data);
+	}
+
+	return count;
+}
+
+static ssize_t show_fan_mode(struct class *cls,
+			 struct class_attribute *attr, char *buf)
+{
+	return sprintf(buf, "Fan mode: %d\n", g_mcu_data->fan_data.mode);
+}
+
+static ssize_t store_fan_mode(struct class *cls, struct class_attribute *attr,
+		       const char *buf, size_t count)
+{
+	int mode;
+
+	if (kstrtoint(buf, 0, &mode))
+		return -EINVAL;
+
+	// 0: manual, 1: auto
+	if( mode >= 0 && mode < 2 ){
+		g_mcu_data->fan_data.mode = mode;
+		khadas_fan_set(&g_mcu_data->fan_data);
+	}
+
+	return count;
+}
+
+static ssize_t show_fan_level(struct class *cls,
+			 struct class_attribute *attr, char *buf)
+{
+	return sprintf(buf, "Fan level: %d\n", g_mcu_data->fan_data.level);
+}
+
+static ssize_t store_fan_level(struct class *cls, struct class_attribute *attr,
+		       const char *buf, size_t count)
+{
+	int level;
+
+	if (kstrtoint(buf, 0, &level))
+		return -EINVAL;
+
+	if( level >= 0 && level < 4){
+		g_mcu_data->fan_data.level = level;
+		khadas_fan_set(&g_mcu_data->fan_data);
+	}
+
+	return count;
+}
+
+static ssize_t show_fan_temp(struct class *cls,
+			 struct class_attribute *attr, char *buf)
+{
+	int temp = -EINVAL;
+
+	if ((KHADAS_BOARD_VIM1 == g_mcu_data->board) ||
+		(KHADAS_BOARD_VIM2 == g_mcu_data->board))
+		temp = meson_gx_get_temperature();
+	else if (KHADAS_BOARD_VIM3 == g_mcu_data->board)
+		temp = meson_g12_get_temperature();
+
+	return sprintf(buf, "cpu_temp:%d\nFan trigger temperature: level0:%d level1:%d level2:%d\n", temp, g_mcu_data->fan_data.trig_temp_level0, g_mcu_data->fan_data.trig_temp_level1, g_mcu_data->fan_data.trig_temp_level2);
+}
+
+static ssize_t store_usb_pcie_switch_mode(struct class *cls, struct class_attribute *attr,
+                const char *buf, size_t count)
+{
+	int ret;
+	int mode;
+
+	if (kstrtoint(buf, 0, &mode))
+		return -EINVAL;
+
+	if (0 != mode && 1 != mode)
+		return -EINVAL;
+
+	if ((mode < MCU_USB_PCIE_SWITCH_MODE_USB3) || (mode > MCU_USB_PCIE_SWITCH_MODE_PCIE))
+		return -EINVAL;
+
+	g_mcu_data->usb_pcie_switch_mode = (u8)mode;
+	ret = mcu_i2c_write_regs(g_mcu_data->client, MCU_USB_PCIE_SWITCH_REG, &g_mcu_data->usb_pcie_switch_mode, 1);
+	if (ret < 0) {
+		printk("write USB PCIe switch error\n");
+
+		return ret;
+	}
+
+	printk("Set USB PCIe Switch Mode: %s\n", g_mcu_data->usb_pcie_switch_mode ? "PCIe" : "USB3.0");
+
+	return count;
+}
+
+static ssize_t show_usb_pcie_switch_mode(struct class *cls,
+                struct class_attribute *attr, char *buf)
+{
+	printk("USB PCIe Switch Mode: %s\n", g_mcu_data->usb_pcie_switch_mode ? "PCIe" : "USB3.0");
+	return sprintf(buf, "%d\n", g_mcu_data->usb_pcie_switch_mode);
+}
+
+static ssize_t store_mcu_poweroff(struct class *cls, struct class_attribute *attr,
+                const char *buf, size_t count)
+{
+	int ret;
+	int val;
+	u8 reg;
+
+	if (kstrtoint(buf, 0, &val))
+		return -EINVAL;
+
+	if (1 != val)
+		return -EINVAL;
+
+	reg = (u8)val;
+	ret = mcu_i2c_write_regs(g_mcu_data->client, MCU_PWR_OFF_CMD_REG, &reg, 1);
+	if (ret < 0) {
+		printk("write poweroff cmd error\n");
+
+		return ret;
+	}
+
+	return count;
+}
+
+static ssize_t store_mcu_rst(struct class *cls, struct class_attribute *attr,
+            const char *buf, size_t count)
+{
+	u8 reg[2];
+	int ret;
+	int rst;
+
+	if (kstrtoint(buf, 0, &rst))
+		return -EINVAL;
+
+	reg[0] = rst;
+	ret = mcu_i2c_write_regs(g_mcu_data->client, MCU_SHUTDOWN_NORMAL_REG, reg, 1);
+	if (ret < 0)
+		printk("rst mcu err\n");
+
+	return count;
+}
+
+static struct class_attribute fan_class_attrs[] = {
+	__ATTR(enable, 0644, show_fan_enable, store_fan_enable),
+	__ATTR(mode, 0644, show_fan_mode, store_fan_mode),
+	__ATTR(level, 0644, show_fan_level, store_fan_level),
+	__ATTR(temp, 0644, show_fan_temp, NULL),
+};
+
+static struct class_attribute mcu_class_attrs[] = {
+	__ATTR(poweroff, 0644, NULL, store_mcu_poweroff),
+	__ATTR(usb_pcie_switch_mode, 0644, show_usb_pcie_switch_mode, store_usb_pcie_switch_mode),
+	__ATTR(rst, 0644, NULL, store_mcu_rst),
+};
+
+static void create_mcu_attrs(void)
+{
+	int i;
+	printk("%s\n",__func__);
+
+	g_mcu_data->mcu_class = class_create(THIS_MODULE, "mcu");
+	if (IS_ERR(g_mcu_data->mcu_class)) {
+		pr_err("create mcu_class debug class fail\n");
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(mcu_class_attrs); i++) {
+		if (strstr(mcu_class_attrs[i].attr.name, "usb_pcie_switch_mode")) {
+			if (!is_mcu_usb_pcie_switch_supported())
+				continue;
+		}
+		if (class_create_file(g_mcu_data->mcu_class, &mcu_class_attrs[i]))
+			pr_err("create mcu attribute %s fail\n", mcu_class_attrs[i].attr.name);
+	}
+
+	if (is_mcu_fan_control_supported()) {
+		g_mcu_data->fan_data.fan_class = class_create(THIS_MODULE, "fan");
+		if (IS_ERR(g_mcu_data->fan_data.fan_class)) {
+			pr_err("create fan_class debug class fail\n");
+			return;
+		}
+
+		for (i=0; i<ARRAY_SIZE(fan_class_attrs); i++) {
+			if (class_create_file(g_mcu_data->fan_data.fan_class, &fan_class_attrs[i]))
+				pr_err("create fan attribute %s fail\n", fan_class_attrs[i].attr.name);
+		}
+	}
+}
+
+static int mcu_parse_dt(struct device *dev)
+{
+	int ret;
+	const char *hwver = NULL;
+
+	if (NULL == dev) return -EINVAL;
+
+	// Get hardwere version
+	ret = of_property_read_string(dev->of_node, "hwver", &hwver);
+	if (ret < 0) {
+		g_mcu_data->hwver = KHADAS_BOARD_HWVER_V12;
+		g_mcu_data->board = KHADAS_BOARD_VIM2;
+	} else {
+		if (strstr(hwver, "VIM1"))
+			g_mcu_data->board = KHADAS_BOARD_VIM1;
+		else if (strstr(hwver, "VIM2"))
+			g_mcu_data->board = KHADAS_BOARD_VIM2;
+		else if (strstr(hwver, "VIM3"))
+			g_mcu_data->board = KHADAS_BOARD_VIM3;
+		else
+			g_mcu_data->board = KHADAS_BOARD_NONE;
+
+		if (KHADAS_BOARD_VIM1 == g_mcu_data->board) {
+			if (0 == strcmp(hwver, "VIM1.V13")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V13;
+			} else if (0 == strcmp(hwver, "VIM1.V14")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V14;
+			} else {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_NONE;
+			}
+		} else if (KHADAS_BOARD_VIM2 == g_mcu_data->board) {
+			if (0 == strcmp(hwver, "VIM2.V12")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V12;
+			} else if (0 == strcmp(hwver, "VIM2.V13")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V13;
+			} else if (0 == strcmp(hwver, "VIM2.V14")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V14;
+			} else {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_NONE;
+			}
+		} else if (KHADAS_BOARD_VIM3 == g_mcu_data->board) {
+			if (0 == strcmp(hwver, "VIM3.V11")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V11;
+			} else if (0 == strcmp(hwver, "VIM3.V12")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V12;
+			} else if (0 == strcmp(hwver, "VIM3.V13")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V13;
+			} else if (0 == strcmp(hwver, "VIM3.V14")) {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_V14;
+			} else {
+				g_mcu_data->hwver = KHADAS_BOARD_HWVER_NONE;
+			}
+		}
+	}
+
+	ret = of_property_read_u32(dev->of_node, "fan,trig_temp_level0", &g_mcu_data->fan_data.trig_temp_level0);
+	if (ret < 0)
+		g_mcu_data->fan_data.trig_temp_level0 = MCU_FAN_TRIG_TEMP_LEVEL0;
+	ret = of_property_read_u32(dev->of_node, "fan,trig_temp_level1", &g_mcu_data->fan_data.trig_temp_level1);
+	if (ret < 0)
+		g_mcu_data->fan_data.trig_temp_level1 = MCU_FAN_TRIG_TEMP_LEVEL1;
+	ret = of_property_read_u32(dev->of_node, "fan,trig_temp_level2", &g_mcu_data->fan_data.trig_temp_level2);
+	if (ret < 0)
+		g_mcu_data->fan_data.trig_temp_level2 = MCU_FAN_TRIG_TEMP_LEVEL2;
+
+	return ret;
+}
+
+static int mcu_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	u8 reg[2];
+	int ret;
+
+	printk("%s\n", __func__);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return -ENODEV;
+
+	g_mcu_data = kzalloc(sizeof(struct mcu_data), GFP_KERNEL);
+
+	if (g_mcu_data == NULL)
+		return -ENOMEM;
+
+	mcu_parse_dt(&client->dev);
+
+	printk("%s: board: %s (%d), hwver: %s (%d)\n", __func__, 
+													mcu_board_type_to_str(g_mcu_data->board),
+													(int)g_mcu_data->board,
+													mcu_board_hardware_version_str(g_mcu_data->hwver),
+													(int)g_mcu_data->hwver);
+
+	g_mcu_data->client = client;
+
+	if (is_mcu_usb_pcie_switch_supported()) {
+		// Get USB PCIe Switch status
+		ret = mcu_i2c_read_regs(client, MCU_USB_PCIE_SWITCH_REG, reg, 1);
+		if (ret < 0)
+			goto exit;
+		g_mcu_data->usb_pcie_switch_mode = (u8)reg[0];
+	}
+
+	if (is_mcu_fan_control_supported()) {
+		g_mcu_data->fan_data.mode = MCU_FAN_MODE_AUTO;
+		g_mcu_data->fan_data.level = MCU_FAN_LEVEL_0;
+		g_mcu_data->fan_data.enable = MCU_FAN_STATUS_DISABLE;
+
+		INIT_DELAYED_WORK(&g_mcu_data->fan_data.work, fan_work_func);
+		mcu_fan_level_set(&g_mcu_data->fan_data, 0);
+	}
+	create_mcu_attrs();
+
+	return 0;
+exit:
+	return ret;
+}
+
+
+static int mcu_remove(struct i2c_client *client)
+{
+	return 0;
+}
+
+static void khadas_fan_shutdown(struct i2c_client *client)
+{
+	g_mcu_data->fan_data.enable = MCU_FAN_STATUS_DISABLE;
+	khadas_fan_set(&g_mcu_data->fan_data);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int khadas_fan_suspend(struct device *dev)
+{
+	cancel_delayed_work(&g_mcu_data->fan_data.work);
+	mcu_fan_level_set(&g_mcu_data->fan_data, 0);
+
+	return 0;
+}
+
+static int khadas_fan_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops fan_dev_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(khadas_fan_suspend, khadas_fan_resume)
+};
+
+#define FAN_PM_OPS (&(fan_dev_pm_ops))
+
+#endif
+
+static const struct i2c_device_id mcu_id[] = {
+	{ "khadas-mcu", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, mcu_id);
+
+
+static struct of_device_id mcu_dt_ids[] = {
+	{ .compatible = "khadas-mcu" },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, mcu_dt_ids);
+
+struct i2c_driver mcu_driver = {
+	.driver  = {
+		.name   = "khadas-mcu",
+		.owner  = THIS_MODULE,
+		.of_match_table = of_match_ptr(mcu_dt_ids),
+#ifdef CONFIG_PM_SLEEP
+		.pm = FAN_PM_OPS,
+#endif
+	},
+	.probe		= mcu_probe,
+	.remove 	= mcu_remove,
+	.shutdown = khadas_fan_shutdown,
+	.id_table	= mcu_id,
+};
+module_i2c_driver(mcu_driver);
+
+MODULE_AUTHOR("Nick <nick@khadas.com>");
+MODULE_DESCRIPTION("Khadas MCU control driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c
index ccb1fe18e993..3947f3c1f91f 100644
--- a/drivers/thermal/amlogic_thermal.c
+++ b/drivers/thermal/amlogic_thermal.c
@@ -104,6 +104,8 @@ struct amlogic_thermal {
 	u32 trim_info;
 };
 
+static struct amlogic_thermal *amlogic_thermal_data_ptr;
+
 /*
  * Calculate a temperature value from a temperature code.
  * The unit of the temperature is degree milliCelsius.
@@ -194,6 +196,21 @@ static int amlogic_thermal_get_temp(void *data, int *temp)
 	return 0;
 }
 
+int meson_g12_get_temperature(void)
+{
+	int temp;
+	int ret;
+
+	ret = amlogic_thermal_get_temp(amlogic_thermal_data_ptr, &temp);
+	if (ret) {
+		printk("amlogic_thermal_get_temp() failed!\n");
+		return ret;
+	}
+
+	return temp / 1000;
+}
+EXPORT_SYMBOL(meson_g12_get_temperature);
+
 static const struct thermal_zone_of_device_ops amlogic_thermal_ops = {
 	.get_temp	= amlogic_thermal_get_temp,
 };
@@ -248,6 +265,8 @@ static int amlogic_thermal_probe(struct platform_device *pdev)
 	if (!pdata)
 		return -ENOMEM;
 
+	amlogic_thermal_data_ptr = pdata;
+
 	pdata->data = of_device_get_match_data(dev);
 	pdata->pdev = pdev;
 	platform_set_drvdata(pdev, pdata);
-- 
2.17.1

