aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thermal/armada_thermal.c
diff options
context:
space:
mode:
authorEzequiel Garcia <ezequiel.garcia@free-electrons.com>2013-04-01 21:37:41 -0400
committerZhang Rui <rui.zhang@intel.com>2013-04-02 09:04:09 -0400
commitfa0d654c84c7705d90a2492b4611e1da7ccdf69c (patch)
tree1886969009b9aeea4cb805545c3a7730b6403953 /drivers/thermal/armada_thermal.c
parent57df8106932b57427df1eaaa13871857f75b1194 (diff)
thermal: Add driver for Armada 370/XP SoC thermal management
This driver supports both Armada 370 and Armada XP SoC thermal management controllers. Armada 370 has a register to check a valid temperature, whereas Armada XP does not. Each has a different initialization (i.e. calibration) function. The temperature conversion formula is the same for both. The controller present in each SoC have a very similar feature set, so it corresponds to have one driver to support both of them. Although this driver may present similarities to Dove and Kirkwood thermal driver, the exact differences and coincidences are not fully known. For this reason, support is given through a separate driver. Signed-off-by: Ezequiel Garcia <ezequiel.garcia@free-electrons.com> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
Diffstat (limited to 'drivers/thermal/armada_thermal.c')
-rw-r--r--drivers/thermal/armada_thermal.c232
1 files changed, 232 insertions, 0 deletions
diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c
new file mode 100644
index 000000000000..5b4d75fd7b49
--- /dev/null
+++ b/drivers/thermal/armada_thermal.c
@@ -0,0 +1,232 @@
1/*
2 * Marvell Armada 370/XP thermal sensor driver
3 *
4 * Copyright (C) 2013 Marvell
5 *
6 * This software is licensed under the terms of the GNU General Public
7 * License version 2, as published by the Free Software Foundation, and
8 * may be copied, distributed, and modified under those terms.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 */
16#include <linux/device.h>
17#include <linux/err.h>
18#include <linux/io.h>
19#include <linux/kernel.h>
20#include <linux/of.h>
21#include <linux/module.h>
22#include <linux/delay.h>
23#include <linux/platform_device.h>
24#include <linux/of_device.h>
25#include <linux/thermal.h>
26
27#define THERMAL_VALID_OFFSET 9
28#define THERMAL_VALID_MASK 0x1
29#define THERMAL_TEMP_OFFSET 10
30#define THERMAL_TEMP_MASK 0x1ff
31
32/* Thermal Manager Control and Status Register */
33#define PMU_TDC0_SW_RST_MASK (0x1 << 1)
34#define PMU_TM_DISABLE_OFFS 0
35#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS)
36#define PMU_TDC0_REF_CAL_CNT_OFFS 11
37#define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS)
38#define PMU_TDC0_OTF_CAL_MASK (0x1 << 30)
39#define PMU_TDC0_START_CAL_MASK (0x1 << 25)
40
41struct armada_thermal_ops;
42
43/* Marvell EBU Thermal Sensor Dev Structure */
44struct armada_thermal_priv {
45 void __iomem *sensor;
46 void __iomem *control;
47 struct armada_thermal_ops *ops;
48};
49
50struct armada_thermal_ops {
51 /* Initialize the sensor */
52 void (*init_sensor)(struct armada_thermal_priv *);
53
54 /* Test for a valid sensor value (optional) */
55 bool (*is_valid)(struct armada_thermal_priv *);
56};
57
58static void armadaxp_init_sensor(struct armada_thermal_priv *priv)
59{
60 unsigned long reg;
61
62 reg = readl_relaxed(priv->control);
63 reg |= PMU_TDC0_OTF_CAL_MASK;
64 writel(reg, priv->control);
65
66 /* Reference calibration value */
67 reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
68 reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
69 writel(reg, priv->control);
70
71 /* Reset the sensor */
72 reg = readl_relaxed(priv->control);
73 writel((reg | PMU_TDC0_SW_RST_MASK), priv->control);
74
75 writel(reg, priv->control);
76
77 /* Enable the sensor */
78 reg = readl_relaxed(priv->sensor);
79 reg &= ~PMU_TM_DISABLE_MASK;
80 writel(reg, priv->sensor);
81}
82
83static void armada370_init_sensor(struct armada_thermal_priv *priv)
84{
85 unsigned long reg;
86
87 reg = readl_relaxed(priv->control);
88 reg |= PMU_TDC0_OTF_CAL_MASK;
89 writel(reg, priv->control);
90
91 /* Reference calibration value */
92 reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
93 reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
94 writel(reg, priv->control);
95
96 reg &= ~PMU_TDC0_START_CAL_MASK;
97 writel(reg, priv->control);
98
99 mdelay(10);
100}
101
102static bool armada_is_valid(struct armada_thermal_priv *priv)
103{
104 unsigned long reg = readl_relaxed(priv->sensor);
105
106 return (reg >> THERMAL_VALID_OFFSET) & THERMAL_VALID_MASK;
107}
108
109static int armada_get_temp(struct thermal_zone_device *thermal,
110 unsigned long *temp)
111{
112 struct armada_thermal_priv *priv = thermal->devdata;
113 unsigned long reg;
114
115 /* Valid check */
116 if (priv->ops->is_valid && !priv->ops->is_valid(priv)) {
117 dev_err(&thermal->device,
118 "Temperature sensor reading not valid\n");
119 return -EIO;
120 }
121
122 reg = readl_relaxed(priv->sensor);
123 reg = (reg >> THERMAL_TEMP_OFFSET) & THERMAL_TEMP_MASK;
124 *temp = (3153000000UL - (10000000UL*reg)) / 13825;
125 return 0;
126}
127
128static struct thermal_zone_device_ops ops = {
129 .get_temp = armada_get_temp,
130};
131
132static const struct armada_thermal_ops armadaxp_ops = {
133 .init_sensor = armadaxp_init_sensor,
134};
135
136static const struct armada_thermal_ops armada370_ops = {
137 .is_valid = armada_is_valid,
138 .init_sensor = armada370_init_sensor,
139};
140
141static const struct of_device_id armada_thermal_id_table[] = {
142 {
143 .compatible = "marvell,armadaxp-thermal",
144 .data = &armadaxp_ops,
145 },
146 {
147 .compatible = "marvell,armada370-thermal",
148 .data = &armada370_ops,
149 },
150 {
151 /* sentinel */
152 },
153};
154MODULE_DEVICE_TABLE(of, armada_thermal_id_table);
155
156static int armada_thermal_probe(struct platform_device *pdev)
157{
158 struct thermal_zone_device *thermal;
159 const struct of_device_id *match;
160 struct armada_thermal_priv *priv;
161 struct resource *res;
162
163 match = of_match_device(armada_thermal_id_table, &pdev->dev);
164 if (!match)
165 return -ENODEV;
166
167 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
168 if (!priv)
169 return -ENOMEM;
170
171 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
172 if (!res) {
173 dev_err(&pdev->dev, "Failed to get platform resource\n");
174 return -ENODEV;
175 }
176
177 priv->sensor = devm_ioremap_resource(&pdev->dev, res);
178 if (IS_ERR(priv->sensor))
179 return PTR_ERR(priv->sensor);
180
181 res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
182 if (!res) {
183 dev_err(&pdev->dev, "Failed to get platform resource\n");
184 return -ENODEV;
185 }
186
187 priv->control = devm_ioremap_resource(&pdev->dev, res);
188 if (IS_ERR(priv->control))
189 return PTR_ERR(priv->control);
190
191 priv->ops = (struct armada_thermal_ops *)match->data;
192 priv->ops->init_sensor(priv);
193
194 thermal = thermal_zone_device_register("armada_thermal", 0, 0,
195 priv, &ops, NULL, 0, 0);
196 if (IS_ERR(thermal)) {
197 dev_err(&pdev->dev,
198 "Failed to register thermal zone device\n");
199 return PTR_ERR(thermal);
200 }
201
202 platform_set_drvdata(pdev, thermal);
203
204 return 0;
205}
206
207static int armada_thermal_exit(struct platform_device *pdev)
208{
209 struct thermal_zone_device *armada_thermal =
210 platform_get_drvdata(pdev);
211
212 thermal_zone_device_unregister(armada_thermal);
213 platform_set_drvdata(pdev, NULL);
214
215 return 0;
216}
217
218static struct platform_driver armada_thermal_driver = {
219 .probe = armada_thermal_probe,
220 .remove = armada_thermal_exit,
221 .driver = {
222 .name = "armada_thermal",
223 .owner = THIS_MODULE,
224 .of_match_table = of_match_ptr(armada_thermal_id_table),
225 },
226};
227
228module_platform_driver(armada_thermal_driver);
229
230MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>");
231MODULE_DESCRIPTION("Armada 370/XP thermal driver");
232MODULE_LICENSE("GPL v2");