build/patch/kernel/meson64-current/board-VIM1-add-simple-MCU-driver-for-FAN.patch
2021-01-14 01:36:40 +01:00

923 lines
24 KiB
Diff

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