diff options
author | Baoyou Xie <baoyou.xie@linaro.org> | 2017-02-06 19:56:41 -0500 |
---|---|---|
committer | Eduardo Valentin <edubezval@gmail.com> | 2017-02-18 19:36:12 -0500 |
commit | 50fdd36f336a03b9486f1cd4b0d85fcb361baf60 (patch) | |
tree | 0e237ed828a37a8410cefbc8ef85a659e8440c99 /drivers/thermal | |
parent | e0fa56489f21e319d0aa9654834209faa3eb481d (diff) |
thermal: zx2967: add thermal driver for ZTE's zx2967 family
This patch adds thermal driver for ZTE's zx2967 family.
Signed-off-by: Baoyou Xie <baoyou.xie@linaro.org>
Reviewed-by: Mathieu Poirier <mathieu.poirier@linaro.org>
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
Diffstat (limited to 'drivers/thermal')
-rw-r--r-- | drivers/thermal/Kconfig | 8 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/zx2967_thermal.c | 258 |
3 files changed, 267 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 3912d24a07b1..776b34396144 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig | |||
@@ -445,4 +445,12 @@ depends on (ARCH_QCOM && OF) || COMPILE_TEST | |||
445 | source "drivers/thermal/qcom/Kconfig" | 445 | source "drivers/thermal/qcom/Kconfig" |
446 | endmenu | 446 | endmenu |
447 | 447 | ||
448 | config ZX2967_THERMAL | ||
449 | tristate "Thermal sensors on zx2967 SoC" | ||
450 | depends on ARCH_ZX || COMPILE_TEST | ||
451 | help | ||
452 | Enable the zx2967 thermal sensors driver, which supports | ||
453 | the primitive temperature sensor embedded in zx2967 SoCs. | ||
454 | This sensor generates the real time die temperature. | ||
455 | |||
448 | endif | 456 | endif |
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index adcf3cc90859..7adae2029355 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile | |||
@@ -57,3 +57,4 @@ obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/ | |||
57 | obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o | 57 | obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o |
58 | obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o | 58 | obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o |
59 | obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o | 59 | obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o |
60 | obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o | ||
diff --git a/drivers/thermal/zx2967_thermal.c b/drivers/thermal/zx2967_thermal.c new file mode 100644 index 000000000000..a5670ad2cfc8 --- /dev/null +++ b/drivers/thermal/zx2967_thermal.c | |||
@@ -0,0 +1,258 @@ | |||
1 | /* | ||
2 | * ZTE's zx2967 family thermal sensor driver | ||
3 | * | ||
4 | * Copyright (C) 2017 ZTE Ltd. | ||
5 | * | ||
6 | * Author: Baoyou Xie <baoyou.xie@linaro.org> | ||
7 | * | ||
8 | * License terms: GNU General Public License (GPL) version 2 | ||
9 | */ | ||
10 | |||
11 | #include <linux/clk.h> | ||
12 | #include <linux/device.h> | ||
13 | #include <linux/err.h> | ||
14 | #include <linux/iopoll.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/thermal.h> | ||
18 | |||
19 | /* Power Mode: 0->low 1->high */ | ||
20 | #define ZX2967_THERMAL_POWER_MODE 0 | ||
21 | #define ZX2967_POWER_MODE_LOW 0 | ||
22 | #define ZX2967_POWER_MODE_HIGH 1 | ||
23 | |||
24 | /* DCF Control Register */ | ||
25 | #define ZX2967_THERMAL_DCF 0x4 | ||
26 | #define ZX2967_DCF_EN BIT(1) | ||
27 | #define ZX2967_DCF_FREEZE BIT(0) | ||
28 | |||
29 | /* Selection Register */ | ||
30 | #define ZX2967_THERMAL_SEL 0x8 | ||
31 | |||
32 | /* Control Register */ | ||
33 | #define ZX2967_THERMAL_CTRL 0x10 | ||
34 | |||
35 | #define ZX2967_THERMAL_READY BIT(12) | ||
36 | #define ZX2967_THERMAL_TEMP_MASK GENMASK(11, 0) | ||
37 | #define ZX2967_THERMAL_ID_MASK 0x18 | ||
38 | #define ZX2967_THERMAL_ID 0x10 | ||
39 | |||
40 | #define ZX2967_GET_TEMP_TIMEOUT_US (100 * 1024) | ||
41 | |||
42 | /** | ||
43 | * struct zx2967_thermal_priv - zx2967 thermal sensor private structure | ||
44 | * @tzd: struct thermal_zone_device where the sensor is registered | ||
45 | * @lock: prevents read sensor in parallel | ||
46 | * @clk_topcrm: topcrm clk structure | ||
47 | * @clk_apb: apb clk structure | ||
48 | * @regs: pointer to base address of the thermal sensor | ||
49 | */ | ||
50 | |||
51 | struct zx2967_thermal_priv { | ||
52 | struct thermal_zone_device *tzd; | ||
53 | struct mutex lock; | ||
54 | struct clk *clk_topcrm; | ||
55 | struct clk *clk_apb; | ||
56 | void __iomem *regs; | ||
57 | struct device *dev; | ||
58 | }; | ||
59 | |||
60 | static int zx2967_thermal_get_temp(void *data, int *temp) | ||
61 | { | ||
62 | void __iomem *regs; | ||
63 | struct zx2967_thermal_priv *priv = data; | ||
64 | u32 val; | ||
65 | int ret; | ||
66 | |||
67 | if (!priv->tzd) | ||
68 | return -EAGAIN; | ||
69 | |||
70 | regs = priv->regs; | ||
71 | mutex_lock(&priv->lock); | ||
72 | writel_relaxed(ZX2967_POWER_MODE_LOW, | ||
73 | regs + ZX2967_THERMAL_POWER_MODE); | ||
74 | writel_relaxed(ZX2967_DCF_EN, regs + ZX2967_THERMAL_DCF); | ||
75 | |||
76 | val = readl_relaxed(regs + ZX2967_THERMAL_SEL); | ||
77 | val &= ~ZX2967_THERMAL_ID_MASK; | ||
78 | val |= ZX2967_THERMAL_ID; | ||
79 | writel_relaxed(val, regs + ZX2967_THERMAL_SEL); | ||
80 | |||
81 | /* | ||
82 | * Must wait for a while, surely it's a bit odd. | ||
83 | * otherwise temperature value we got has a few deviation, even if | ||
84 | * the THERMAL_READY bit is set. | ||
85 | */ | ||
86 | usleep_range(100, 300); | ||
87 | ret = readx_poll_timeout(readl, regs + ZX2967_THERMAL_CTRL, | ||
88 | val, val & ZX2967_THERMAL_READY, 300, | ||
89 | ZX2967_GET_TEMP_TIMEOUT_US); | ||
90 | if (ret) { | ||
91 | dev_err(priv->dev, "Thermal sensor data timeout\n"); | ||
92 | goto unlock; | ||
93 | } | ||
94 | |||
95 | writel_relaxed(ZX2967_DCF_FREEZE | ZX2967_DCF_EN, | ||
96 | regs + ZX2967_THERMAL_DCF); | ||
97 | val = readl_relaxed(regs + ZX2967_THERMAL_CTRL) | ||
98 | & ZX2967_THERMAL_TEMP_MASK; | ||
99 | writel_relaxed(ZX2967_POWER_MODE_HIGH, | ||
100 | regs + ZX2967_THERMAL_POWER_MODE); | ||
101 | |||
102 | /* | ||
103 | * Calculate temperature | ||
104 | * In dts, slope is multiplied by 1000. | ||
105 | */ | ||
106 | *temp = DIV_ROUND_CLOSEST(((s32)val + priv->tzd->tzp->offset) * 1000, | ||
107 | priv->tzd->tzp->slope); | ||
108 | |||
109 | unlock: | ||
110 | mutex_unlock(&priv->lock); | ||
111 | return ret; | ||
112 | } | ||
113 | |||
114 | static struct thermal_zone_of_device_ops zx2967_of_thermal_ops = { | ||
115 | .get_temp = zx2967_thermal_get_temp, | ||
116 | }; | ||
117 | |||
118 | static int zx2967_thermal_probe(struct platform_device *pdev) | ||
119 | { | ||
120 | struct zx2967_thermal_priv *priv; | ||
121 | struct resource *res; | ||
122 | int ret; | ||
123 | |||
124 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | ||
125 | if (!priv) | ||
126 | return -ENOMEM; | ||
127 | |||
128 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
129 | priv->regs = devm_ioremap_resource(&pdev->dev, res); | ||
130 | if (IS_ERR(priv->regs)) | ||
131 | return PTR_ERR(priv->regs); | ||
132 | |||
133 | priv->clk_topcrm = devm_clk_get(&pdev->dev, "topcrm"); | ||
134 | if (IS_ERR(priv->clk_topcrm)) { | ||
135 | ret = PTR_ERR(priv->clk_topcrm); | ||
136 | dev_err(&pdev->dev, "failed to get topcrm clock: %d\n", ret); | ||
137 | return ret; | ||
138 | } | ||
139 | |||
140 | ret = clk_prepare_enable(priv->clk_topcrm); | ||
141 | if (ret) { | ||
142 | dev_err(&pdev->dev, "failed to enable topcrm clock: %d\n", | ||
143 | ret); | ||
144 | return ret; | ||
145 | } | ||
146 | |||
147 | priv->clk_apb = devm_clk_get(&pdev->dev, "apb"); | ||
148 | if (IS_ERR(priv->clk_apb)) { | ||
149 | ret = PTR_ERR(priv->clk_apb); | ||
150 | dev_err(&pdev->dev, "failed to get apb clock: %d\n", ret); | ||
151 | goto disable_clk_topcrm; | ||
152 | } | ||
153 | |||
154 | ret = clk_prepare_enable(priv->clk_apb); | ||
155 | if (ret) { | ||
156 | dev_err(&pdev->dev, "failed to enable apb clock: %d\n", | ||
157 | ret); | ||
158 | goto disable_clk_topcrm; | ||
159 | } | ||
160 | |||
161 | mutex_init(&priv->lock); | ||
162 | priv->tzd = thermal_zone_of_sensor_register(&pdev->dev, | ||
163 | 0, priv, &zx2967_of_thermal_ops); | ||
164 | |||
165 | if (IS_ERR(priv->tzd)) { | ||
166 | ret = PTR_ERR(priv->tzd); | ||
167 | dev_err(&pdev->dev, "failed to register sensor: %d\n", ret); | ||
168 | goto disable_clk_all; | ||
169 | } | ||
170 | |||
171 | if (priv->tzd->tzp->slope == 0) { | ||
172 | thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd); | ||
173 | dev_err(&pdev->dev, "coefficients of sensor is invalid\n"); | ||
174 | ret = -EINVAL; | ||
175 | goto disable_clk_all; | ||
176 | } | ||
177 | |||
178 | priv->dev = &pdev->dev; | ||
179 | platform_set_drvdata(pdev, priv); | ||
180 | |||
181 | return 0; | ||
182 | |||
183 | disable_clk_all: | ||
184 | clk_disable_unprepare(priv->clk_apb); | ||
185 | disable_clk_topcrm: | ||
186 | clk_disable_unprepare(priv->clk_topcrm); | ||
187 | return ret; | ||
188 | } | ||
189 | |||
190 | static int zx2967_thermal_exit(struct platform_device *pdev) | ||
191 | { | ||
192 | struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev); | ||
193 | |||
194 | thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd); | ||
195 | clk_disable_unprepare(priv->clk_topcrm); | ||
196 | clk_disable_unprepare(priv->clk_apb); | ||
197 | |||
198 | return 0; | ||
199 | } | ||
200 | |||
201 | static const struct of_device_id zx2967_thermal_id_table[] = { | ||
202 | { .compatible = "zte,zx296718-thermal" }, | ||
203 | {} | ||
204 | }; | ||
205 | MODULE_DEVICE_TABLE(of, zx2967_thermal_id_table); | ||
206 | |||
207 | #ifdef CONFIG_PM_SLEEP | ||
208 | static int zx2967_thermal_suspend(struct device *dev) | ||
209 | { | ||
210 | struct platform_device *pdev = to_platform_device(dev); | ||
211 | struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev); | ||
212 | |||
213 | if (priv && priv->clk_topcrm) | ||
214 | clk_disable_unprepare(priv->clk_topcrm); | ||
215 | |||
216 | if (priv && priv->clk_apb) | ||
217 | clk_disable_unprepare(priv->clk_apb); | ||
218 | |||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | static int zx2967_thermal_resume(struct device *dev) | ||
223 | { | ||
224 | struct platform_device *pdev = to_platform_device(dev); | ||
225 | struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev); | ||
226 | int error; | ||
227 | |||
228 | error = clk_prepare_enable(priv->clk_topcrm); | ||
229 | if (error) | ||
230 | return error; | ||
231 | |||
232 | error = clk_prepare_enable(priv->clk_apb); | ||
233 | if (error) { | ||
234 | clk_disable_unprepare(priv->clk_topcrm); | ||
235 | return error; | ||
236 | } | ||
237 | |||
238 | return 0; | ||
239 | } | ||
240 | #endif | ||
241 | |||
242 | static SIMPLE_DEV_PM_OPS(zx2967_thermal_pm_ops, | ||
243 | zx2967_thermal_suspend, zx2967_thermal_resume); | ||
244 | |||
245 | static struct platform_driver zx2967_thermal_driver = { | ||
246 | .probe = zx2967_thermal_probe, | ||
247 | .remove = zx2967_thermal_exit, | ||
248 | .driver = { | ||
249 | .name = "zx2967_thermal", | ||
250 | .of_match_table = zx2967_thermal_id_table, | ||
251 | .pm = &zx2967_thermal_pm_ops, | ||
252 | }, | ||
253 | }; | ||
254 | module_platform_driver(zx2967_thermal_driver); | ||
255 | |||
256 | MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>"); | ||
257 | MODULE_DESCRIPTION("ZTE zx2967 thermal driver"); | ||
258 | MODULE_LICENSE("GPL v2"); | ||