Merge remote-tracking branch 'torvalds/master' into perf/urgent
[linux-2.6-microblaze.git] / drivers / thermal / khadas_mcu_fan.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Khadas MCU Controlled FAN driver
4  *
5  * Copyright (C) 2020 BayLibre SAS
6  * Author(s): Neil Armstrong <narmstrong@baylibre.com>
7  */
8
9 #include <linux/module.h>
10 #include <linux/of.h>
11 #include <linux/platform_device.h>
12 #include <linux/mfd/khadas-mcu.h>
13 #include <linux/regmap.h>
14 #include <linux/sysfs.h>
15 #include <linux/thermal.h>
16
17 #define MAX_LEVEL 3
18
19 struct khadas_mcu_fan_ctx {
20         struct khadas_mcu *mcu;
21         unsigned int level;
22         struct thermal_cooling_device *cdev;
23 };
24
25 static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
26                                     unsigned int level)
27 {
28         int ret;
29
30         ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
31                            level);
32         if (ret)
33                 return ret;
34
35         ctx->level = level;
36
37         return 0;
38 }
39
40 static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
41                                         unsigned long *state)
42 {
43         *state = MAX_LEVEL;
44
45         return 0;
46 }
47
48 static int khadas_mcu_fan_get_cur_state(struct thermal_cooling_device *cdev,
49                                         unsigned long *state)
50 {
51         struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
52
53         *state = ctx->level;
54
55         return 0;
56 }
57
58 static int
59 khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
60                              unsigned long state)
61 {
62         struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
63
64         if (state > MAX_LEVEL)
65                 return -EINVAL;
66
67         if (state == ctx->level)
68                 return 0;
69
70         return khadas_mcu_fan_set_level(ctx, state);
71 }
72
73 static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
74         .get_max_state = khadas_mcu_fan_get_max_state,
75         .get_cur_state = khadas_mcu_fan_get_cur_state,
76         .set_cur_state = khadas_mcu_fan_set_cur_state,
77 };
78
79 static int khadas_mcu_fan_probe(struct platform_device *pdev)
80 {
81         struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
82         struct thermal_cooling_device *cdev;
83         struct device *dev = &pdev->dev;
84         struct khadas_mcu_fan_ctx *ctx;
85         int ret;
86
87         ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
88         if (!ctx)
89                 return -ENOMEM;
90         ctx->mcu = mcu;
91         platform_set_drvdata(pdev, ctx);
92
93         cdev = devm_thermal_of_cooling_device_register(dev->parent,
94                         dev->parent->of_node, "khadas-mcu-fan", ctx,
95                         &khadas_mcu_fan_cooling_ops);
96         if (IS_ERR(cdev)) {
97                 ret = PTR_ERR(cdev);
98                 dev_err(dev, "Failed to register khadas-mcu-fan as cooling device: %d\n",
99                         ret);
100                 return ret;
101         }
102         ctx->cdev = cdev;
103
104         return 0;
105 }
106
107 static void khadas_mcu_fan_shutdown(struct platform_device *pdev)
108 {
109         struct khadas_mcu_fan_ctx *ctx = platform_get_drvdata(pdev);
110
111         khadas_mcu_fan_set_level(ctx, 0);
112 }
113
114 #ifdef CONFIG_PM_SLEEP
115 static int khadas_mcu_fan_suspend(struct device *dev)
116 {
117         struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev);
118         unsigned int level_save = ctx->level;
119         int ret;
120
121         ret = khadas_mcu_fan_set_level(ctx, 0);
122         if (ret)
123                 return ret;
124
125         ctx->level = level_save;
126
127         return 0;
128 }
129
130 static int khadas_mcu_fan_resume(struct device *dev)
131 {
132         struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev);
133
134         return khadas_mcu_fan_set_level(ctx, ctx->level);
135 }
136 #endif
137
138 static SIMPLE_DEV_PM_OPS(khadas_mcu_fan_pm, khadas_mcu_fan_suspend,
139                          khadas_mcu_fan_resume);
140
141 static const struct platform_device_id khadas_mcu_fan_id_table[] = {
142         { .name = "khadas-mcu-fan-ctrl", },
143         {},
144 };
145 MODULE_DEVICE_TABLE(platform, khadas_mcu_fan_id_table);
146
147 static struct platform_driver khadas_mcu_fan_driver = {
148         .probe          = khadas_mcu_fan_probe,
149         .shutdown       = khadas_mcu_fan_shutdown,
150         .driver = {
151                 .name           = "khadas-mcu-fan-ctrl",
152                 .pm             = &khadas_mcu_fan_pm,
153         },
154         .id_table       = khadas_mcu_fan_id_table,
155 };
156
157 module_platform_driver(khadas_mcu_fan_driver);
158
159 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
160 MODULE_DESCRIPTION("Khadas MCU FAN driver");
161 MODULE_LICENSE("GPL");