Commit | Line | Data |
---|---|---|
af873fce | 1 | // SPDX-License-Identifier: GPL-2.0-only |
50fdd36f BX |
2 | /* |
3 | * ZTE's zx2967 family thermal sensor driver | |
4 | * | |
5 | * Copyright (C) 2017 ZTE Ltd. | |
6 | * | |
7 | * Author: Baoyou Xie <baoyou.xie@linaro.org> | |
50fdd36f BX |
8 | */ |
9 | ||
10 | #include <linux/clk.h> | |
11 | #include <linux/device.h> | |
12 | #include <linux/err.h> | |
13 | #include <linux/iopoll.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/thermal.h> | |
17 | ||
18 | /* Power Mode: 0->low 1->high */ | |
19 | #define ZX2967_THERMAL_POWER_MODE 0 | |
20 | #define ZX2967_POWER_MODE_LOW 0 | |
21 | #define ZX2967_POWER_MODE_HIGH 1 | |
22 | ||
23 | /* DCF Control Register */ | |
24 | #define ZX2967_THERMAL_DCF 0x4 | |
25 | #define ZX2967_DCF_EN BIT(1) | |
26 | #define ZX2967_DCF_FREEZE BIT(0) | |
27 | ||
28 | /* Selection Register */ | |
29 | #define ZX2967_THERMAL_SEL 0x8 | |
30 | ||
31 | /* Control Register */ | |
32 | #define ZX2967_THERMAL_CTRL 0x10 | |
33 | ||
34 | #define ZX2967_THERMAL_READY BIT(12) | |
35 | #define ZX2967_THERMAL_TEMP_MASK GENMASK(11, 0) | |
36 | #define ZX2967_THERMAL_ID_MASK 0x18 | |
37 | #define ZX2967_THERMAL_ID 0x10 | |
38 | ||
39 | #define ZX2967_GET_TEMP_TIMEOUT_US (100 * 1024) | |
40 | ||
41 | /** | |
42 | * struct zx2967_thermal_priv - zx2967 thermal sensor private structure | |
43 | * @tzd: struct thermal_zone_device where the sensor is registered | |
44 | * @lock: prevents read sensor in parallel | |
45 | * @clk_topcrm: topcrm clk structure | |
46 | * @clk_apb: apb clk structure | |
47 | * @regs: pointer to base address of the thermal sensor | |
48 | */ | |
49 | ||
50 | struct zx2967_thermal_priv { | |
51 | struct thermal_zone_device *tzd; | |
52 | struct mutex lock; | |
53 | struct clk *clk_topcrm; | |
54 | struct clk *clk_apb; | |
55 | void __iomem *regs; | |
56 | struct device *dev; | |
57 | }; | |
58 | ||
59 | static int zx2967_thermal_get_temp(void *data, int *temp) | |
60 | { | |
61 | void __iomem *regs; | |
62 | struct zx2967_thermal_priv *priv = data; | |
63 | u32 val; | |
64 | int ret; | |
65 | ||
66 | if (!priv->tzd) | |
67 | return -EAGAIN; | |
68 | ||
69 | regs = priv->regs; | |
70 | mutex_lock(&priv->lock); | |
71 | writel_relaxed(ZX2967_POWER_MODE_LOW, | |
72 | regs + ZX2967_THERMAL_POWER_MODE); | |
73 | writel_relaxed(ZX2967_DCF_EN, regs + ZX2967_THERMAL_DCF); | |
74 | ||
75 | val = readl_relaxed(regs + ZX2967_THERMAL_SEL); | |
76 | val &= ~ZX2967_THERMAL_ID_MASK; | |
77 | val |= ZX2967_THERMAL_ID; | |
78 | writel_relaxed(val, regs + ZX2967_THERMAL_SEL); | |
79 | ||
80 | /* | |
81 | * Must wait for a while, surely it's a bit odd. | |
82 | * otherwise temperature value we got has a few deviation, even if | |
83 | * the THERMAL_READY bit is set. | |
84 | */ | |
85 | usleep_range(100, 300); | |
86 | ret = readx_poll_timeout(readl, regs + ZX2967_THERMAL_CTRL, | |
87 | val, val & ZX2967_THERMAL_READY, 300, | |
88 | ZX2967_GET_TEMP_TIMEOUT_US); | |
89 | if (ret) { | |
90 | dev_err(priv->dev, "Thermal sensor data timeout\n"); | |
91 | goto unlock; | |
92 | } | |
93 | ||
94 | writel_relaxed(ZX2967_DCF_FREEZE | ZX2967_DCF_EN, | |
95 | regs + ZX2967_THERMAL_DCF); | |
96 | val = readl_relaxed(regs + ZX2967_THERMAL_CTRL) | |
97 | & ZX2967_THERMAL_TEMP_MASK; | |
98 | writel_relaxed(ZX2967_POWER_MODE_HIGH, | |
99 | regs + ZX2967_THERMAL_POWER_MODE); | |
100 | ||
101 | /* | |
102 | * Calculate temperature | |
103 | * In dts, slope is multiplied by 1000. | |
104 | */ | |
105 | *temp = DIV_ROUND_CLOSEST(((s32)val + priv->tzd->tzp->offset) * 1000, | |
106 | priv->tzd->tzp->slope); | |
107 | ||
108 | unlock: | |
109 | mutex_unlock(&priv->lock); | |
110 | return ret; | |
111 | } | |
112 | ||
e85c995f | 113 | static const struct thermal_zone_of_device_ops zx2967_of_thermal_ops = { |
50fdd36f BX |
114 | .get_temp = zx2967_thermal_get_temp, |
115 | }; | |
116 | ||
117 | static int zx2967_thermal_probe(struct platform_device *pdev) | |
118 | { | |
119 | struct zx2967_thermal_priv *priv; | |
120 | struct resource *res; | |
121 | int ret; | |
122 | ||
123 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
124 | if (!priv) | |
125 | return -ENOMEM; | |
126 | ||
127 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
128 | priv->regs = devm_ioremap_resource(&pdev->dev, res); | |
129 | if (IS_ERR(priv->regs)) | |
130 | return PTR_ERR(priv->regs); | |
131 | ||
132 | priv->clk_topcrm = devm_clk_get(&pdev->dev, "topcrm"); | |
133 | if (IS_ERR(priv->clk_topcrm)) { | |
134 | ret = PTR_ERR(priv->clk_topcrm); | |
135 | dev_err(&pdev->dev, "failed to get topcrm clock: %d\n", ret); | |
136 | return ret; | |
137 | } | |
138 | ||
139 | ret = clk_prepare_enable(priv->clk_topcrm); | |
140 | if (ret) { | |
141 | dev_err(&pdev->dev, "failed to enable topcrm clock: %d\n", | |
142 | ret); | |
143 | return ret; | |
144 | } | |
145 | ||
146 | priv->clk_apb = devm_clk_get(&pdev->dev, "apb"); | |
147 | if (IS_ERR(priv->clk_apb)) { | |
148 | ret = PTR_ERR(priv->clk_apb); | |
149 | dev_err(&pdev->dev, "failed to get apb clock: %d\n", ret); | |
150 | goto disable_clk_topcrm; | |
151 | } | |
152 | ||
153 | ret = clk_prepare_enable(priv->clk_apb); | |
154 | if (ret) { | |
155 | dev_err(&pdev->dev, "failed to enable apb clock: %d\n", | |
156 | ret); | |
157 | goto disable_clk_topcrm; | |
158 | } | |
159 | ||
160 | mutex_init(&priv->lock); | |
161 | priv->tzd = thermal_zone_of_sensor_register(&pdev->dev, | |
162 | 0, priv, &zx2967_of_thermal_ops); | |
163 | ||
164 | if (IS_ERR(priv->tzd)) { | |
165 | ret = PTR_ERR(priv->tzd); | |
166 | dev_err(&pdev->dev, "failed to register sensor: %d\n", ret); | |
167 | goto disable_clk_all; | |
168 | } | |
169 | ||
170 | if (priv->tzd->tzp->slope == 0) { | |
171 | thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd); | |
172 | dev_err(&pdev->dev, "coefficients of sensor is invalid\n"); | |
173 | ret = -EINVAL; | |
174 | goto disable_clk_all; | |
175 | } | |
176 | ||
177 | priv->dev = &pdev->dev; | |
178 | platform_set_drvdata(pdev, priv); | |
179 | ||
180 | return 0; | |
181 | ||
182 | disable_clk_all: | |
183 | clk_disable_unprepare(priv->clk_apb); | |
184 | disable_clk_topcrm: | |
185 | clk_disable_unprepare(priv->clk_topcrm); | |
186 | return ret; | |
187 | } | |
188 | ||
189 | static int zx2967_thermal_exit(struct platform_device *pdev) | |
190 | { | |
191 | struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev); | |
192 | ||
193 | thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd); | |
194 | clk_disable_unprepare(priv->clk_topcrm); | |
195 | clk_disable_unprepare(priv->clk_apb); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | static const struct of_device_id zx2967_thermal_id_table[] = { | |
201 | { .compatible = "zte,zx296718-thermal" }, | |
202 | {} | |
203 | }; | |
204 | MODULE_DEVICE_TABLE(of, zx2967_thermal_id_table); | |
205 | ||
206 | #ifdef CONFIG_PM_SLEEP | |
207 | static int zx2967_thermal_suspend(struct device *dev) | |
208 | { | |
209d07e6 | 209 | struct zx2967_thermal_priv *priv = dev_get_drvdata(dev); |
50fdd36f BX |
210 | |
211 | if (priv && priv->clk_topcrm) | |
212 | clk_disable_unprepare(priv->clk_topcrm); | |
213 | ||
214 | if (priv && priv->clk_apb) | |
215 | clk_disable_unprepare(priv->clk_apb); | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static int zx2967_thermal_resume(struct device *dev) | |
221 | { | |
209d07e6 | 222 | struct zx2967_thermal_priv *priv = dev_get_drvdata(dev); |
50fdd36f BX |
223 | int error; |
224 | ||
225 | error = clk_prepare_enable(priv->clk_topcrm); | |
226 | if (error) | |
227 | return error; | |
228 | ||
229 | error = clk_prepare_enable(priv->clk_apb); | |
230 | if (error) { | |
231 | clk_disable_unprepare(priv->clk_topcrm); | |
232 | return error; | |
233 | } | |
234 | ||
235 | return 0; | |
236 | } | |
237 | #endif | |
238 | ||
239 | static SIMPLE_DEV_PM_OPS(zx2967_thermal_pm_ops, | |
240 | zx2967_thermal_suspend, zx2967_thermal_resume); | |
241 | ||
242 | static struct platform_driver zx2967_thermal_driver = { | |
243 | .probe = zx2967_thermal_probe, | |
244 | .remove = zx2967_thermal_exit, | |
245 | .driver = { | |
246 | .name = "zx2967_thermal", | |
247 | .of_match_table = zx2967_thermal_id_table, | |
248 | .pm = &zx2967_thermal_pm_ops, | |
249 | }, | |
250 | }; | |
251 | module_platform_driver(zx2967_thermal_driver); | |
252 | ||
253 | MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>"); | |
254 | MODULE_DESCRIPTION("ZTE zx2967 thermal driver"); | |
255 | MODULE_LICENSE("GPL v2"); |