diff options
-rw-r--r-- | Documentation/devicetree/bindings/thermal/armada-thermal.txt | 22 | ||||
-rw-r--r-- | drivers/thermal/Kconfig | 8 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/armada_thermal.c | 232 |
4 files changed, 263 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/thermal/armada-thermal.txt b/Documentation/devicetree/bindings/thermal/armada-thermal.txt new file mode 100644 index 000000000000..fff93d5f92de --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/armada-thermal.txt | |||
@@ -0,0 +1,22 @@ | |||
1 | * Marvell Armada 370/XP thermal management | ||
2 | |||
3 | Required properties: | ||
4 | |||
5 | - compatible: Should be set to one of the following: | ||
6 | marvell,armada370-thermal | ||
7 | marvell,armadaxp-thermal | ||
8 | |||
9 | - reg: Device's register space. | ||
10 | Two entries are expected, see the examples below. | ||
11 | The first one is required for the sensor register; | ||
12 | the second one is required for the control register | ||
13 | to be used for sensor initialization (a.k.a. calibration). | ||
14 | |||
15 | Example: | ||
16 | |||
17 | thermal@d0018300 { | ||
18 | compatible = "marvell,armada370-thermal"; | ||
19 | reg = <0xd0018300 0x4 | ||
20 | 0xd0018304 0x4>; | ||
21 | status = "okay"; | ||
22 | }; | ||
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index a764f165b589..9eddf744c94f 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig | |||
@@ -144,6 +144,14 @@ config DB8500_THERMAL | |||
144 | created. Cooling devices can be bound to the trip points to cool this | 144 | created. Cooling devices can be bound to the trip points to cool this |
145 | thermal zone if trip points reached. | 145 | thermal zone if trip points reached. |
146 | 146 | ||
147 | config ARMADA_THERMAL | ||
148 | tristate "Armada 370/XP thermal management" | ||
149 | depends on ARCH_MVEBU | ||
150 | depends on OF | ||
151 | help | ||
152 | Enable this option if you want to have support for thermal management | ||
153 | controller present in Armada 370 and Armada XP SoC. | ||
154 | |||
147 | config DB8500_CPUFREQ_COOLING | 155 | config DB8500_CPUFREQ_COOLING |
148 | tristate "DB8500 cpufreq cooling" | 156 | tristate "DB8500 cpufreq cooling" |
149 | depends on ARCH_U8500 | 157 | depends on ARCH_U8500 |
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index d3a2b38c31e8..7f6509a97c14 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile | |||
@@ -19,6 +19,7 @@ obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o | |||
19 | obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o | 19 | obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o |
20 | obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o | 20 | obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o |
21 | obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o | 21 | obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o |
22 | obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o | ||
22 | obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o | 23 | obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o |
23 | obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o | 24 | obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o |
24 | 25 | ||
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 | |||
41 | struct armada_thermal_ops; | ||
42 | |||
43 | /* Marvell EBU Thermal Sensor Dev Structure */ | ||
44 | struct armada_thermal_priv { | ||
45 | void __iomem *sensor; | ||
46 | void __iomem *control; | ||
47 | struct armada_thermal_ops *ops; | ||
48 | }; | ||
49 | |||
50 | struct 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 | |||
58 | static 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 | |||
83 | static 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 | |||
102 | static 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 | |||
109 | static 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 | |||
128 | static struct thermal_zone_device_ops ops = { | ||
129 | .get_temp = armada_get_temp, | ||
130 | }; | ||
131 | |||
132 | static const struct armada_thermal_ops armadaxp_ops = { | ||
133 | .init_sensor = armadaxp_init_sensor, | ||
134 | }; | ||
135 | |||
136 | static const struct armada_thermal_ops armada370_ops = { | ||
137 | .is_valid = armada_is_valid, | ||
138 | .init_sensor = armada370_init_sensor, | ||
139 | }; | ||
140 | |||
141 | static 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 | }; | ||
154 | MODULE_DEVICE_TABLE(of, armada_thermal_id_table); | ||
155 | |||
156 | static 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 | |||
207 | static 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 | |||
218 | static 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 | |||
228 | module_platform_driver(armada_thermal_driver); | ||
229 | |||
230 | MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>"); | ||
231 | MODULE_DESCRIPTION("Armada 370/XP thermal driver"); | ||
232 | MODULE_LICENSE("GPL v2"); | ||