diff options
Diffstat (limited to 'drivers/thermal/rcar_thermal.c')
-rw-r--r-- | drivers/thermal/rcar_thermal.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c new file mode 100644 index 000000000000..f7a1b574a304 --- /dev/null +++ b/drivers/thermal/rcar_thermal.c | |||
@@ -0,0 +1,260 @@ | |||
1 | /* | ||
2 | * R-Car THS/TSC thermal sensor driver | ||
3 | * | ||
4 | * Copyright (C) 2012 Renesas Solutions Corp. | ||
5 | * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; version 2 of the License. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | ||
19 | */ | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/err.h> | ||
22 | #include <linux/io.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/slab.h> | ||
26 | #include <linux/spinlock.h> | ||
27 | #include <linux/thermal.h> | ||
28 | |||
29 | #define THSCR 0x2c | ||
30 | #define THSSR 0x30 | ||
31 | |||
32 | /* THSCR */ | ||
33 | #define CPTAP 0xf | ||
34 | |||
35 | /* THSSR */ | ||
36 | #define CTEMP 0x3f | ||
37 | |||
38 | |||
39 | struct rcar_thermal_priv { | ||
40 | void __iomem *base; | ||
41 | struct device *dev; | ||
42 | spinlock_t lock; | ||
43 | u32 comp; | ||
44 | }; | ||
45 | |||
46 | /* | ||
47 | * basic functions | ||
48 | */ | ||
49 | static u32 rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg) | ||
50 | { | ||
51 | unsigned long flags; | ||
52 | u32 ret; | ||
53 | |||
54 | spin_lock_irqsave(&priv->lock, flags); | ||
55 | |||
56 | ret = ioread32(priv->base + reg); | ||
57 | |||
58 | spin_unlock_irqrestore(&priv->lock, flags); | ||
59 | |||
60 | return ret; | ||
61 | } | ||
62 | |||
63 | #if 0 /* no user at this point */ | ||
64 | static void rcar_thermal_write(struct rcar_thermal_priv *priv, | ||
65 | u32 reg, u32 data) | ||
66 | { | ||
67 | unsigned long flags; | ||
68 | |||
69 | spin_lock_irqsave(&priv->lock, flags); | ||
70 | |||
71 | iowrite32(data, priv->base + reg); | ||
72 | |||
73 | spin_unlock_irqrestore(&priv->lock, flags); | ||
74 | } | ||
75 | #endif | ||
76 | |||
77 | static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, | ||
78 | u32 mask, u32 data) | ||
79 | { | ||
80 | unsigned long flags; | ||
81 | u32 val; | ||
82 | |||
83 | spin_lock_irqsave(&priv->lock, flags); | ||
84 | |||
85 | val = ioread32(priv->base + reg); | ||
86 | val &= ~mask; | ||
87 | val |= (data & mask); | ||
88 | iowrite32(val, priv->base + reg); | ||
89 | |||
90 | spin_unlock_irqrestore(&priv->lock, flags); | ||
91 | } | ||
92 | |||
93 | /* | ||
94 | * zone device functions | ||
95 | */ | ||
96 | static int rcar_thermal_get_temp(struct thermal_zone_device *zone, | ||
97 | unsigned long *temp) | ||
98 | { | ||
99 | struct rcar_thermal_priv *priv = zone->devdata; | ||
100 | int val, min, max, tmp; | ||
101 | |||
102 | tmp = -200; /* default */ | ||
103 | while (1) { | ||
104 | if (priv->comp < 1 || priv->comp > 12) { | ||
105 | dev_err(priv->dev, | ||
106 | "THSSR invalid data (%d)\n", priv->comp); | ||
107 | priv->comp = 4; /* for next thermal */ | ||
108 | return -EINVAL; | ||
109 | } | ||
110 | |||
111 | /* | ||
112 | * THS comparator offset and the reference temperature | ||
113 | * | ||
114 | * Comparator | reference | Temperature field | ||
115 | * offset | temperature | measurement | ||
116 | * | (degrees C) | (degrees C) | ||
117 | * -------------+---------------+------------------- | ||
118 | * 1 | -45 | -45 to -30 | ||
119 | * 2 | -30 | -30 to -15 | ||
120 | * 3 | -15 | -15 to 0 | ||
121 | * 4 | 0 | 0 to +15 | ||
122 | * 5 | +15 | +15 to +30 | ||
123 | * 6 | +30 | +30 to +45 | ||
124 | * 7 | +45 | +45 to +60 | ||
125 | * 8 | +60 | +60 to +75 | ||
126 | * 9 | +75 | +75 to +90 | ||
127 | * 10 | +90 | +90 to +105 | ||
128 | * 11 | +105 | +105 to +120 | ||
129 | * 12 | +120 | +120 to +135 | ||
130 | */ | ||
131 | |||
132 | /* calculate thermal limitation */ | ||
133 | min = (priv->comp * 15) - 60; | ||
134 | max = min + 15; | ||
135 | |||
136 | /* | ||
137 | * we need to wait 300us after changing comparator offset | ||
138 | * to get stable temperature. | ||
139 | * see "Usage Notes" on datasheet | ||
140 | */ | ||
141 | rcar_thermal_bset(priv, THSCR, CPTAP, priv->comp); | ||
142 | udelay(300); | ||
143 | |||
144 | /* calculate current temperature */ | ||
145 | val = rcar_thermal_read(priv, THSSR) & CTEMP; | ||
146 | val = (val * 5) - 65; | ||
147 | |||
148 | dev_dbg(priv->dev, "comp/min/max/val = %d/%d/%d/%d\n", | ||
149 | priv->comp, min, max, val); | ||
150 | |||
151 | /* | ||
152 | * If val is same as min/max, then, | ||
153 | * it should try again on next comparator. | ||
154 | * But the val might be correct temperature. | ||
155 | * Keep it on "tmp" and compare with next val. | ||
156 | */ | ||
157 | if (tmp == val) | ||
158 | break; | ||
159 | |||
160 | if (val <= min) { | ||
161 | tmp = min; | ||
162 | priv->comp--; /* try again */ | ||
163 | } else if (val >= max) { | ||
164 | tmp = max; | ||
165 | priv->comp++; /* try again */ | ||
166 | } else { | ||
167 | tmp = val; | ||
168 | break; | ||
169 | } | ||
170 | } | ||
171 | |||
172 | *temp = tmp; | ||
173 | return 0; | ||
174 | } | ||
175 | |||
176 | static struct thermal_zone_device_ops rcar_thermal_zone_ops = { | ||
177 | .get_temp = rcar_thermal_get_temp, | ||
178 | }; | ||
179 | |||
180 | /* | ||
181 | * platform functions | ||
182 | */ | ||
183 | static int rcar_thermal_probe(struct platform_device *pdev) | ||
184 | { | ||
185 | struct thermal_zone_device *zone; | ||
186 | struct rcar_thermal_priv *priv; | ||
187 | struct resource *res; | ||
188 | int ret; | ||
189 | |||
190 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
191 | if (!res) { | ||
192 | dev_err(&pdev->dev, "Could not get platform resource\n"); | ||
193 | return -ENODEV; | ||
194 | } | ||
195 | |||
196 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | ||
197 | if (!priv) { | ||
198 | dev_err(&pdev->dev, "Could not allocate priv\n"); | ||
199 | return -ENOMEM; | ||
200 | } | ||
201 | |||
202 | priv->comp = 4; /* basic setup */ | ||
203 | priv->dev = &pdev->dev; | ||
204 | spin_lock_init(&priv->lock); | ||
205 | priv->base = devm_ioremap_nocache(&pdev->dev, | ||
206 | res->start, resource_size(res)); | ||
207 | if (!priv->base) { | ||
208 | dev_err(&pdev->dev, "Unable to ioremap thermal register\n"); | ||
209 | ret = -ENOMEM; | ||
210 | goto error_free_priv; | ||
211 | } | ||
212 | |||
213 | zone = thermal_zone_device_register("rcar_thermal", 0, 0, priv, | ||
214 | &rcar_thermal_zone_ops, 0, 0); | ||
215 | if (IS_ERR(zone)) { | ||
216 | dev_err(&pdev->dev, "thermal zone device is NULL\n"); | ||
217 | ret = PTR_ERR(zone); | ||
218 | goto error_iounmap; | ||
219 | } | ||
220 | |||
221 | platform_set_drvdata(pdev, zone); | ||
222 | |||
223 | dev_info(&pdev->dev, "proved\n"); | ||
224 | |||
225 | return 0; | ||
226 | |||
227 | error_iounmap: | ||
228 | devm_iounmap(&pdev->dev, priv->base); | ||
229 | error_free_priv: | ||
230 | devm_kfree(&pdev->dev, priv); | ||
231 | |||
232 | return ret; | ||
233 | } | ||
234 | |||
235 | static int rcar_thermal_remove(struct platform_device *pdev) | ||
236 | { | ||
237 | struct thermal_zone_device *zone = platform_get_drvdata(pdev); | ||
238 | struct rcar_thermal_priv *priv = zone->devdata; | ||
239 | |||
240 | thermal_zone_device_unregister(zone); | ||
241 | platform_set_drvdata(pdev, NULL); | ||
242 | |||
243 | devm_iounmap(&pdev->dev, priv->base); | ||
244 | devm_kfree(&pdev->dev, priv); | ||
245 | |||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | static struct platform_driver rcar_thermal_driver = { | ||
250 | .driver = { | ||
251 | .name = "rcar_thermal", | ||
252 | }, | ||
253 | .probe = rcar_thermal_probe, | ||
254 | .remove = rcar_thermal_remove, | ||
255 | }; | ||
256 | module_platform_driver(rcar_thermal_driver); | ||
257 | |||
258 | MODULE_LICENSE("GPL"); | ||
259 | MODULE_DESCRIPTION("R-Car THS/TSC thermal sensor driver"); | ||
260 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | ||