Commit | Line | Data |
---|---|---|
50fdd36f BX |
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 | ||
e85c995f | 114 | static const struct thermal_zone_of_device_ops zx2967_of_thermal_ops = { |
50fdd36f BX |
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"); |