summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS6
-rw-r--r--drivers/platform/x86/Kconfig12
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/gpd-pocket-fan.c193
4 files changed, 212 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 825acf165c2d..bc4d77695918 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5947,6 +5947,12 @@ L: linux-input@vger.kernel.org
5947S: Maintained 5947S: Maintained
5948F: drivers/input/touchscreen/goodix.c 5948F: drivers/input/touchscreen/goodix.c
5949 5949
5950GPD POCKET FAN DRIVER
5951M: Hans de Goede <hdegoede@redhat.com>
5952L: platform-driver-x86@vger.kernel.org
5953S: Maintained
5954F: drivers/platform/x86/gpd-pocket-fan.c
5955
5950GPIO ACPI SUPPORT 5956GPIO ACPI SUPPORT
5951M: Mika Westerberg <mika.westerberg@linux.intel.com> 5957M: Mika Westerberg <mika.westerberg@linux.intel.com>
5952M: Andy Shevchenko <andriy.shevchenko@linux.intel.com> 5958M: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 88bf2f46b7a4..8a07df54a67e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -258,6 +258,18 @@ config AMILO_RFKILL
258 This is a driver for enabling wifi on some Fujitsu-Siemens Amilo 258 This is a driver for enabling wifi on some Fujitsu-Siemens Amilo
259 laptops. 259 laptops.
260 260
261config GPD_POCKET_FAN
262 tristate "GPD Pocket Fan Controller support"
263 depends on ACPI
264 depends on THERMAL
265 ---help---
266 Driver for the GPD Pocket vendor specific FAN02501 ACPI device
267 which controls the fan speed on the GPD Pocket.
268
269 Without this driver the fan on the Pocket will stay off independent
270 of the CPU temperature. Say Y or M if the kernel may be used on a
271 GPD pocket.
272
261config TC1100_WMI 273config TC1100_WMI
262 tristate "HP Compaq TC1100 Tablet WMI Extras" 274 tristate "HP Compaq TC1100 Tablet WMI Extras"
263 depends on !X86_64 275 depends on !X86_64
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 4f92dbf30900..57f37e55786a 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_HP_ACCEL) += hp_accel.o
29obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o 29obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o
30obj-$(CONFIG_HP_WMI) += hp-wmi.o 30obj-$(CONFIG_HP_WMI) += hp-wmi.o
31obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o 31obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o
32obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o
32obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o 33obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
33obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o 34obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
34obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o 35obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c
new file mode 100644
index 000000000000..c6f4d89b1437
--- /dev/null
+++ b/drivers/platform/x86/gpd-pocket-fan.c
@@ -0,0 +1,193 @@
1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * GPD Pocket fan controller driver
4 *
5 * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
6 */
7
8#include <linux/acpi.h>
9#include <linux/gpio/consumer.h>
10#include <linux/module.h>
11#include <linux/moduleparam.h>
12#include <linux/platform_device.h>
13#include <linux/thermal.h>
14#include <linux/workqueue.h>
15
16static int temp_limits[3] = { 55000, 60000, 65000 };
17module_param_array(temp_limits, int, NULL, 0444);
18MODULE_PARM_DESC(temp_limits,
19 "Milli-celcius values above which the fan speed increases");
20
21static int hysteresis = 3000;
22module_param(hysteresis, int, 0444);
23MODULE_PARM_DESC(hysteresis,
24 "Hysteresis in milli-celcius before lowering the fan speed");
25
26struct gpd_pocket_fan_data {
27 struct device *dev;
28 struct thermal_zone_device *dts0;
29 struct thermal_zone_device *dts1;
30 struct gpio_desc *gpio0;
31 struct gpio_desc *gpio1;
32 struct delayed_work work;
33 int last_speed;
34};
35
36static void gpd_pocket_fan_set_speed(struct gpd_pocket_fan_data *fan, int speed)
37{
38 if (speed == fan->last_speed)
39 return;
40
41 gpiod_direction_output(fan->gpio0, !!(speed & 1));
42 gpiod_direction_output(fan->gpio1, !!(speed & 2));
43
44 fan->last_speed = speed;
45}
46
47static void gpd_pocket_fan_worker(struct work_struct *work)
48{
49 struct gpd_pocket_fan_data *fan =
50 container_of(work, struct gpd_pocket_fan_data, work.work);
51 int t0, t1, temp, speed, i;
52
53 if (thermal_zone_get_temp(fan->dts0, &t0) ||
54 thermal_zone_get_temp(fan->dts1, &t1)) {
55 dev_warn(fan->dev, "Error getting temperature\n");
56 queue_delayed_work(system_wq, &fan->work,
57 msecs_to_jiffies(1000));
58 return;
59 }
60
61 temp = max(t0, t1);
62
63 speed = fan->last_speed;
64
65 /* Determine minimum speed */
66 for (i = 0; i < ARRAY_SIZE(temp_limits); i++) {
67 if (temp < temp_limits[i])
68 break;
69 }
70 if (speed < i)
71 speed = i;
72
73 /* Use hysteresis before lowering speed again */
74 for (i = 0; i < ARRAY_SIZE(temp_limits); i++) {
75 if (temp <= (temp_limits[i] - hysteresis))
76 break;
77 }
78 if (speed > i)
79 speed = i;
80
81 if (fan->last_speed <= 0 && speed)
82 speed = 3; /* kick start motor */
83
84 gpd_pocket_fan_set_speed(fan, speed);
85
86 /* When mostly idle (low temp/speed), slow down the poll interval. */
87 queue_delayed_work(system_wq, &fan->work,
88 msecs_to_jiffies(4000 / (speed + 1)));
89}
90
91static void gpd_pocket_fan_force_update(struct gpd_pocket_fan_data *fan)
92{
93 fan->last_speed = -1;
94 mod_delayed_work(system_wq, &fan->work, 0);
95}
96
97static int gpd_pocket_fan_probe(struct platform_device *pdev)
98{
99 struct gpd_pocket_fan_data *fan;
100 int i;
101
102 for (i = 0; i < ARRAY_SIZE(temp_limits); i++) {
103 if (temp_limits[i] < 40000 || temp_limits[i] > 70000) {
104 dev_err(&pdev->dev, "Invalid temp-limit %d (must be between 40000 and 70000)\n",
105 temp_limits[i]);
106 return -EINVAL;
107 }
108 }
109 if (hysteresis < 1000 || hysteresis > 10000) {
110 dev_err(&pdev->dev, "Invalid hysteresis %d (must be between 1000 and 10000)\n",
111 hysteresis);
112 return -EINVAL;
113 }
114
115 fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL);
116 if (!fan)
117 return -ENOMEM;
118
119 fan->dev = &pdev->dev;
120 INIT_DELAYED_WORK(&fan->work, gpd_pocket_fan_worker);
121
122 /* Note this returns a "weak" reference which we don't need to free */
123 fan->dts0 = thermal_zone_get_zone_by_name("soc_dts0");
124 if (IS_ERR(fan->dts0))
125 return -EPROBE_DEFER;
126
127 fan->dts1 = thermal_zone_get_zone_by_name("soc_dts1");
128 if (IS_ERR(fan->dts1))
129 return -EPROBE_DEFER;
130
131 fan->gpio0 = devm_gpiod_get_index(fan->dev, NULL, 0, GPIOD_ASIS);
132 if (IS_ERR(fan->gpio0))
133 return PTR_ERR(fan->gpio0);
134
135 fan->gpio1 = devm_gpiod_get_index(fan->dev, NULL, 1, GPIOD_ASIS);
136 if (IS_ERR(fan->gpio1))
137 return PTR_ERR(fan->gpio1);
138
139 gpd_pocket_fan_force_update(fan);
140
141 platform_set_drvdata(pdev, fan);
142 return 0;
143}
144
145static int gpd_pocket_fan_remove(struct platform_device *pdev)
146{
147 struct gpd_pocket_fan_data *fan = platform_get_drvdata(pdev);
148
149 cancel_delayed_work_sync(&fan->work);
150 return 0;
151}
152
153#ifdef CONFIG_PM_SLEEP
154static int gpd_pocket_fan_suspend(struct device *dev)
155{
156 struct gpd_pocket_fan_data *fan = dev_get_drvdata(dev);
157
158 gpd_pocket_fan_set_speed(fan, 0);
159 return 0;
160}
161
162static int gpd_pocket_fan_resume(struct device *dev)
163{
164 struct gpd_pocket_fan_data *fan = dev_get_drvdata(dev);
165
166 gpd_pocket_fan_force_update(fan);
167 return 0;
168}
169#endif
170static SIMPLE_DEV_PM_OPS(gpd_pocket_fan_pm_ops,
171 gpd_pocket_fan_suspend,
172 gpd_pocket_fan_resume);
173
174static struct acpi_device_id gpd_pocket_fan_acpi_match[] = {
175 { "FAN02501" },
176 {},
177};
178MODULE_DEVICE_TABLE(acpi, gpd_pocket_fan_acpi_match);
179
180static struct platform_driver gpd_pocket_fan_driver = {
181 .probe = gpd_pocket_fan_probe,
182 .remove = gpd_pocket_fan_remove,
183 .driver = {
184 .name = "gpd_pocket_fan",
185 .acpi_match_table = gpd_pocket_fan_acpi_match,
186 .pm = &gpd_pocket_fan_pm_ops,
187 },
188};
189
190module_platform_driver(gpd_pocket_fan_driver);
191MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
192MODULE_DESCRIPTION("GPD pocket fan driver");
193MODULE_LICENSE("GPL");