build/patch/kernel/archive/meson64-5.10/board-VIM2-add-GPIO-FAN-driver-for-V12-version.patch

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