482 lines
13 KiB
Diff
482 lines
13 KiB
Diff
From 037976f20acf9b7d0240fae01870b78580929c24 Mon Sep 17 00:00:00 2001
|
|
From: Nick Xie <nick@khadas.com>
|
|
Date: Wed, 25 Dec 2019 11:32:49 +0800
|
|
Subject: [PATCH 099/101] VIM2: add GPIO FAN driver for V12 version
|
|
|
|
Signed-off-by: Nick Xie <nick@khadas.com>
|
|
---
|
|
.../dts/amlogic/meson-gxm-khadas-vim2.dts | 12 +
|
|
drivers/misc/Kconfig | 6 +
|
|
drivers/misc/Makefile | 1 +
|
|
drivers/misc/khadas-fan.c | 407 ++++++++++++++++++
|
|
4 files changed, 426 insertions(+)
|
|
create mode 100644 drivers/misc/khadas-fan.c
|
|
|
|
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 f9ec3f3efbe1..fc618b72d5b5 100644
|
|
--- a/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts
|
|
+++ b/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts
|
|
@@ -56,7 +56,19 @@
|
|
reset-gpios = <&gpio BOOT_9 GPIO_ACTIVE_LOW>;
|
|
};
|
|
|
|
+ fan {
|
|
+ compatible = "fanctl";
|
|
+ fan_ctl0 = <&gpio GPIODV_14 GPIO_ACTIVE_HIGH>;
|
|
+ fan_ctl1 = <&gpio GPIODV_15 GPIO_ACTIVE_HIGH>;
|
|
+ trig_temp_level0 = <50>;
|
|
+ trig_temp_level1 = <60>;
|
|
+ trig_temp_level2 = <70>;
|
|
+ hwver = "VIM2.V12"; /* Will be updated in uboot. */
|
|
+ status = "okay";
|
|
+ };
|
|
+
|
|
gpio_fan: gpio-fan {
|
|
+ status = "disabled";
|
|
compatible = "gpio-fan";
|
|
gpios = <&gpio GPIODV_14 GPIO_ACTIVE_HIGH
|
|
&gpio GPIODV_15 GPIO_ACTIVE_HIGH>;
|
|
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
|
|
index fb0a3830fd87..264e39ccc330 100644
|
|
--- a/drivers/misc/Kconfig
|
|
+++ b/drivers/misc/Kconfig
|
|
@@ -465,6 +465,12 @@ config PVPANIC
|
|
a paravirtualized device provided by QEMU; it lets a virtual machine
|
|
(guest) communicate panic events to the host.
|
|
|
|
+config KHADAS_FAN
|
|
+ tristate "Khadas FAN"
|
|
+ default y
|
|
+ help
|
|
+ This driver is for Khadas FAN.
|
|
+
|
|
config KHADAS_MCU
|
|
tristate "Khadas boards on-board MCU"
|
|
help
|
|
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
|
|
index 9bbf2a479405..c24a29e12f1f 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_FAN) += khadas-fan.o
|
|
obj-$(CONFIG_KHADAS_MCU) += khadas-mcu.o
|
|
diff --git a/drivers/misc/khadas-fan.c b/drivers/misc/khadas-fan.c
|
|
new file mode 100644
|
|
index 000000000000..ee0fd42a9dae
|
|
--- /dev/null
|
|
+++ b/drivers/misc/khadas-fan.c
|
|
@@ -0,0 +1,407 @@
|
|
+/*
|
|
+ * gpio-fan.c - driver for fans controlled by GPIO.
|
|
+ */
|
|
+#include <linux/module.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/workqueue.h>
|
|
+
|
|
+#define KHADAS_FAN_TRIG_TEMP_LEVEL0 50 // 50 degree if not set
|
|
+#define KHADAS_FAN_TRIG_TEMP_LEVEL1 60 // 60 degree if not set
|
|
+#define KHADAS_FAN_TRIG_TEMP_LEVEL2 70 // 70 degree if not set
|
|
+#define KHADAS_FAN_TRIG_MAXTEMP 80
|
|
+#define KHADAS_FAN_LOOP_SECS 30 * HZ // 30 seconds
|
|
+#define KHADAS_FAN_TEST_LOOP_SECS 5 * HZ // 5 seconds
|
|
+#define KHADAS_FAN_LOOP_NODELAY_SECS 0
|
|
+#define KHADAS_FAN_GPIO_OFF 0
|
|
+#define KHADAS_FAN_GPIO_ON 1
|
|
+
|
|
+enum khadas_fan_mode {
|
|
+ KHADAS_FAN_STATE_MANUAL = 0,
|
|
+ KHADAS_FAN_STATE_AUTO,
|
|
+};
|
|
+
|
|
+enum khadas_fan_level {
|
|
+ KHADAS_FAN_LEVEL_0 = 0,
|
|
+ KHADAS_FAN_LEVEL_1,
|
|
+ KHADAS_FAN_LEVEL_2,
|
|
+ KHADAS_FAN_LEVEL_3,
|
|
+};
|
|
+
|
|
+enum khadas_fan_enable {
|
|
+ KHADAS_FAN_DISABLE = 0,
|
|
+ KHADAS_FAN_ENABLE,
|
|
+};
|
|
+
|
|
+enum khadas_fan_hwver {
|
|
+ KHADAS_FAN_HWVER_NONE = 0,
|
|
+ KHADAS_FAN_HWVER_V12,
|
|
+ KHADAS_FAN_HWVER_V13,
|
|
+ KHADAS_FAN_HWVER_V14
|
|
+};
|
|
+
|
|
+struct khadas_fan_data {
|
|
+ int initialized;
|
|
+ struct platform_device *pdev;
|
|
+ struct class *class;
|
|
+ struct delayed_work work;
|
|
+ struct delayed_work fan_test_work;
|
|
+ enum khadas_fan_enable enable;
|
|
+ enum khadas_fan_mode mode;
|
|
+ enum khadas_fan_level level;
|
|
+ int ctrl_gpio0;
|
|
+ int ctrl_gpio1;
|
|
+ int trig_temp_level0;
|
|
+ int trig_temp_level1;
|
|
+ int trig_temp_level2;
|
|
+ enum khadas_fan_hwver hwver;
|
|
+};
|
|
+
|
|
+struct khadas_fan_data *fan_data = NULL;
|
|
+
|
|
+void khadas_fan_level_set(struct khadas_fan_data *fan_data, int level )
|
|
+{
|
|
+ if(0 == level){
|
|
+ gpio_set_value(fan_data->ctrl_gpio0, KHADAS_FAN_GPIO_OFF);
|
|
+ gpio_set_value(fan_data->ctrl_gpio1, KHADAS_FAN_GPIO_OFF);
|
|
+ }else if(1 == level){
|
|
+ gpio_set_value(fan_data->ctrl_gpio0, KHADAS_FAN_GPIO_ON);
|
|
+ gpio_set_value(fan_data->ctrl_gpio1, KHADAS_FAN_GPIO_OFF);
|
|
+ }else if(2 == level){
|
|
+ gpio_set_value(fan_data->ctrl_gpio0, KHADAS_FAN_GPIO_OFF);
|
|
+ gpio_set_value(fan_data->ctrl_gpio1, KHADAS_FAN_GPIO_ON);
|
|
+ }else if(3 == level){
|
|
+ gpio_set_value(fan_data->ctrl_gpio0, KHADAS_FAN_GPIO_ON);
|
|
+ gpio_set_value(fan_data->ctrl_gpio1, KHADAS_FAN_GPIO_ON);
|
|
+ }
|
|
+}
|
|
+
|
|
+extern int meson_gx_get_temperature(void);
|
|
+static void fan_work_func(struct work_struct *_work)
|
|
+{
|
|
+ int temp = -EINVAL;
|
|
+ struct khadas_fan_data *fan_data = container_of(_work,
|
|
+ struct khadas_fan_data, work.work);
|
|
+
|
|
+ temp = meson_gx_get_temperature();
|
|
+
|
|
+ if(temp != -EINVAL){
|
|
+ if(temp < fan_data->trig_temp_level0 ){
|
|
+ khadas_fan_level_set(fan_data,0);
|
|
+
|
|
+ }else if(temp < fan_data->trig_temp_level1 ){
|
|
+ khadas_fan_level_set(fan_data,1);
|
|
+
|
|
+ }else if(temp < fan_data->trig_temp_level2 ){
|
|
+ khadas_fan_level_set(fan_data,2);
|
|
+
|
|
+ }else{
|
|
+ khadas_fan_level_set(fan_data,3);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ schedule_delayed_work(&fan_data->work, KHADAS_FAN_LOOP_SECS);
|
|
+}
|
|
+
|
|
+//static void fan_test_work_func(struct work_struct *_work)
|
|
+//{
|
|
+// struct khadas_fan_data *fan_data = container_of(_work,
|
|
+// struct khadas_fan_data, fan_test_work.work);
|
|
+//
|
|
+//
|
|
+// khadas_fan_level_set(fan_data,0);
|
|
+//
|
|
+//}
|
|
+
|
|
+
|
|
+static void khadas_fan_set(struct khadas_fan_data *fan_data)
|
|
+{
|
|
+
|
|
+ cancel_delayed_work(&fan_data->work);
|
|
+
|
|
+ if (fan_data->enable == KHADAS_FAN_DISABLE) {
|
|
+ khadas_fan_level_set(fan_data,0);
|
|
+ return;
|
|
+ }
|
|
+ switch (fan_data->mode) {
|
|
+ case KHADAS_FAN_STATE_MANUAL:
|
|
+ switch(fan_data->level){
|
|
+ case KHADAS_FAN_LEVEL_1:
|
|
+ khadas_fan_level_set(fan_data,1);
|
|
+ break;
|
|
+ case KHADAS_FAN_LEVEL_2:
|
|
+ khadas_fan_level_set(fan_data,2);
|
|
+ break;
|
|
+ case KHADAS_FAN_LEVEL_3:
|
|
+ khadas_fan_level_set(fan_data,3);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case KHADAS_FAN_STATE_AUTO:
|
|
+ // FIXME: achieve with a better way
|
|
+ schedule_delayed_work(&fan_data->work, KHADAS_FAN_LOOP_NODELAY_SECS);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static ssize_t fan_enable_show(struct class *cls,
|
|
+ struct class_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "Fan enable: %d\n", fan_data->enable);
|
|
+}
|
|
+
|
|
+static ssize_t fan_enable_store(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 ){
|
|
+ fan_data->enable = enable;
|
|
+ khadas_fan_set(fan_data);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t fan_mode_show(struct class *cls,
|
|
+ struct class_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "Fan mode: %d\n", fan_data->mode);
|
|
+}
|
|
+
|
|
+static ssize_t fan_mode_store(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 ){
|
|
+ fan_data->mode = mode;
|
|
+ khadas_fan_set(fan_data);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t fan_level_show(struct class *cls,
|
|
+ struct class_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "Fan level: %d\n", fan_data->level);
|
|
+}
|
|
+
|
|
+static ssize_t fan_level_store(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){
|
|
+ fan_data->level = level;
|
|
+ khadas_fan_set(fan_data);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+
|
|
+static ssize_t fan_temp_show(struct class *cls,
|
|
+ struct class_attribute *attr, char *buf)
|
|
+{
|
|
+ int temp = -EINVAL;
|
|
+ temp = meson_gx_get_temperature();
|
|
+
|
|
+ return sprintf(buf, "cpu_temp:%d\nFan trigger temperature: level0:%d level1:%d level2:%d\n", temp, fan_data->trig_temp_level0, fan_data->trig_temp_level1, fan_data->trig_temp_level2);
|
|
+}
|
|
+
|
|
+#if 0
|
|
+static ssize_t fan_temp_store(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct khadas_fan_data *fan_data = dev_get_drvdata(dev);
|
|
+ int temp;
|
|
+
|
|
+ if (kstrtoint(buf, 0, &temp))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (temp > KHADAS_FAN_TRIG_MAXTEMP)
|
|
+ temp = KHADAS_FAN_TRIG_MAXTEMP;
|
|
+ fan_data->trig_temp_level0 = temp;
|
|
+
|
|
+ return count;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static struct class_attribute fan_class_attrs[] = {
|
|
+ __ATTR(enable, 0644, fan_enable_show, fan_enable_store),
|
|
+ __ATTR(mode, 0644, fan_mode_show, fan_mode_store),
|
|
+ __ATTR(level, 0644, fan_level_show, fan_level_store),
|
|
+ __ATTR(temp, 0644, fan_temp_show, NULL),
|
|
+};
|
|
+
|
|
+static int khadas_fan_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ int ret;
|
|
+ int i;
|
|
+ const char *hwver = NULL;
|
|
+
|
|
+ printk("khadas_fan_probe\n");
|
|
+
|
|
+ fan_data = devm_kzalloc(dev, sizeof(struct khadas_fan_data), GFP_KERNEL);
|
|
+ if (!fan_data)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ // Get hardwere version
|
|
+ ret = of_property_read_string(dev->of_node, "hwver", &hwver);
|
|
+ if (ret < 0) {
|
|
+ fan_data->hwver = KHADAS_FAN_HWVER_V12;
|
|
+ } else {
|
|
+ if (0 == strcmp(hwver, "VIM2.V12")) {
|
|
+ fan_data->hwver = KHADAS_FAN_HWVER_V12;
|
|
+ } else if (0 == strcmp(hwver, "VIM2.V13")) {
|
|
+ fan_data->hwver = KHADAS_FAN_HWVER_V13;
|
|
+ } else if (0 == strcmp(hwver, "VIM2.V14")) {
|
|
+ fan_data->hwver = KHADAS_FAN_HWVER_V14;
|
|
+ }
|
|
+ else {
|
|
+ fan_data->hwver = KHADAS_FAN_HWVER_NONE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (KHADAS_FAN_HWVER_V12 != fan_data->hwver) {
|
|
+ // This driver is only for Khadas VIM2 V12 version.
|
|
+ printk("FAN: This driver is only for Khadas VIM2 V12 version.\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32(dev->of_node, "trig_temp_level0", &fan_data->trig_temp_level0);
|
|
+ if (ret < 0)
|
|
+ fan_data->trig_temp_level0 = KHADAS_FAN_TRIG_TEMP_LEVEL0;
|
|
+ ret = of_property_read_u32(dev->of_node, "trig_temp_level1", &fan_data->trig_temp_level1);
|
|
+ if (ret < 0)
|
|
+ fan_data->trig_temp_level1 = KHADAS_FAN_TRIG_TEMP_LEVEL1;
|
|
+ ret = of_property_read_u32(dev->of_node, "trig_temp_level2", &fan_data->trig_temp_level2);
|
|
+ if (ret < 0)
|
|
+ fan_data->trig_temp_level2 = KHADAS_FAN_TRIG_TEMP_LEVEL2;
|
|
+
|
|
+ fan_data->ctrl_gpio0 = of_get_named_gpio(dev->of_node, "fan_ctl0", 0);
|
|
+ fan_data->ctrl_gpio1 = of_get_named_gpio(dev->of_node, "fan_ctl1", 0);
|
|
+ if ((gpio_request(fan_data->ctrl_gpio0, "FAN") != 0)|| (gpio_request(fan_data->ctrl_gpio1, "FAN") != 0))
|
|
+ return -EIO;
|
|
+
|
|
+ gpio_direction_output(fan_data->ctrl_gpio0, KHADAS_FAN_GPIO_OFF);
|
|
+ gpio_direction_output(fan_data->ctrl_gpio1, KHADAS_FAN_GPIO_OFF);
|
|
+ fan_data->mode = KHADAS_FAN_STATE_AUTO;
|
|
+ fan_data->level = KHADAS_FAN_LEVEL_0;
|
|
+ fan_data->enable = KHADAS_FAN_DISABLE;
|
|
+
|
|
+ INIT_DELAYED_WORK(&fan_data->work, fan_work_func);
|
|
+ khadas_fan_level_set(fan_data,0);
|
|
+// INIT_DELAYED_WORK(&fan_data->fan_test_work, fan_test_work_func);
|
|
+// schedule_delayed_work(&fan_data->fan_test_work, KHADAS_FAN_TEST_LOOP_SECS);
|
|
+
|
|
+ fan_data->pdev = pdev;
|
|
+ platform_set_drvdata(pdev, fan_data);
|
|
+
|
|
+ fan_data->class = class_create(THIS_MODULE, "fan");
|
|
+ if (IS_ERR(fan_data->class)) {
|
|
+ return PTR_ERR(fan_data->class);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(fan_class_attrs); i++){
|
|
+ ret = class_create_file(fan_data->class, &fan_class_attrs[i]);
|
|
+ if(0!=ret){
|
|
+ printk("khadas_fan_probe,class_create_file%d failed \n", i);
|
|
+ }
|
|
+ }
|
|
+ dev_info(dev, "trigger temperature is level0:%d, level1:%d, level2:%d.\n", fan_data->trig_temp_level0, fan_data->trig_temp_level1, fan_data->trig_temp_level2);
|
|
+
|
|
+ fan_data->initialized = 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int khadas_fan_remove(struct platform_device *pdev)
|
|
+{
|
|
+ if (fan_data->initialized) {
|
|
+ fan_data->enable = KHADAS_FAN_DISABLE;
|
|
+ khadas_fan_set(fan_data);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void khadas_fan_shutdown(struct platform_device *pdev)
|
|
+{
|
|
+ if (fan_data->initialized) {
|
|
+ fan_data->enable = KHADAS_FAN_DISABLE;
|
|
+ khadas_fan_set(fan_data);
|
|
+ }
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int khadas_fan_suspend(struct platform_device *pdev, pm_message_t state)
|
|
+{
|
|
+ if (fan_data->initialized) {
|
|
+ cancel_delayed_work(&fan_data->work);
|
|
+ khadas_fan_level_set(fan_data, 0);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int khadas_fan_resume(struct platform_device *pdev)
|
|
+{
|
|
+ if (fan_data->initialized) {
|
|
+ khadas_fan_set(fan_data);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static struct of_device_id of_khadas_fan_match[] = {
|
|
+ { .compatible = "fanctl", },
|
|
+ {},
|
|
+};
|
|
+
|
|
+static struct platform_driver khadas_fan_driver = {
|
|
+ .probe = khadas_fan_probe,
|
|
+#ifdef CONFIG_PM
|
|
+ .suspend = khadas_fan_suspend,
|
|
+ .resume = khadas_fan_resume,
|
|
+#endif
|
|
+ .remove = khadas_fan_remove,
|
|
+ .shutdown = khadas_fan_shutdown,
|
|
+ .driver = {
|
|
+ .name = "fanctl",
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = of_match_ptr(of_khadas_fan_match),
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(khadas_fan_driver);
|
|
+
|
|
+MODULE_AUTHOR("kenny <kenny@khadas.com>");
|
|
+MODULE_DESCRIPTION("khadas GPIO Fan driver");
|
|
+MODULE_LICENSE("GPL");
|
|
--
|
|
2.17.1
|
|
|