Commit | Line | Data |
---|---|---|
3faee942 AM |
1 | /* |
2 | * drivers/leds/leds-apu.c | |
3 | * Copyright (C) 2017 Alan Mizrahi, alan at mizrahi dot com dot ve | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions are met: | |
7 | * | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. Neither the names of the copyright holders nor the names of its | |
14 | * contributors may be used to endorse or promote products derived from | |
15 | * this software without specific prior written permission. | |
16 | * | |
17 | * Alternatively, this software may be distributed under the terms of the | |
18 | * GNU General Public License ("GPL") version 2 as published by the Free | |
19 | * Software Foundation. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
25 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
28 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
30 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
31 | * POSSIBILITY OF SUCH DAMAGE. | |
32 | */ | |
33 | ||
34 | #include <linux/dmi.h> | |
35 | #include <linux/err.h> | |
36 | #include <linux/init.h> | |
37 | #include <linux/io.h> | |
38 | #include <linux/kernel.h> | |
39 | #include <linux/leds.h> | |
40 | #include <linux/module.h> | |
41 | #include <linux/platform_device.h> | |
42 | ||
43 | #define APU1_FCH_ACPI_MMIO_BASE 0xFED80000 | |
44 | #define APU1_FCH_GPIO_BASE (APU1_FCH_ACPI_MMIO_BASE + 0x01BD) | |
45 | #define APU1_LEDON 0x08 | |
46 | #define APU1_LEDOFF 0xC8 | |
47 | #define APU1_NUM_GPIO 3 | |
48 | #define APU1_IOSIZE sizeof(u8) | |
49 | ||
50 | #define APU2_FCH_ACPI_MMIO_BASE 0xFED80000 | |
51 | #define APU2_FCH_GPIO_BASE (APU2_FCH_ACPI_MMIO_BASE + 0x1500) | |
52 | #define APU2_GPIO_BIT_WRITE 22 | |
53 | #define APU2_APU2_NUM_GPIO 4 | |
54 | #define APU2_IOSIZE sizeof(u32) | |
55 | ||
56 | /* LED access parameters */ | |
57 | struct apu_param { | |
58 | void __iomem *addr; /* for ioread/iowrite */ | |
59 | }; | |
60 | ||
61 | /* LED private data */ | |
62 | struct apu_led_priv { | |
63 | struct led_classdev cdev; | |
64 | struct apu_param param; | |
65 | }; | |
66 | #define cdev_to_priv(c) container_of(c, struct apu_led_priv, cdev) | |
67 | ||
68 | /* LED profile */ | |
69 | struct apu_led_profile { | |
70 | const char *name; | |
71 | enum led_brightness brightness; | |
72 | unsigned long offset; /* for devm_ioremap */ | |
73 | }; | |
74 | ||
75 | /* Supported platform types */ | |
76 | enum apu_led_platform_types { | |
77 | APU1_LED_PLATFORM, | |
78 | APU2_LED_PLATFORM, | |
79 | }; | |
80 | ||
81 | struct apu_led_pdata { | |
82 | struct platform_device *pdev; | |
83 | struct apu_led_priv *pled; | |
84 | const struct apu_led_profile *profile; | |
85 | enum apu_led_platform_types platform; | |
86 | int num_led_instances; | |
87 | int iosize; /* for devm_ioremap() */ | |
88 | spinlock_t lock; | |
89 | }; | |
90 | ||
91 | static struct apu_led_pdata *apu_led; | |
92 | ||
93 | static const struct apu_led_profile apu1_led_profile[] = { | |
94 | { "apu:green:1", LED_ON, APU1_FCH_GPIO_BASE + 0 * APU1_IOSIZE }, | |
95 | { "apu:green:2", LED_OFF, APU1_FCH_GPIO_BASE + 1 * APU1_IOSIZE }, | |
96 | { "apu:green:3", LED_OFF, APU1_FCH_GPIO_BASE + 2 * APU1_IOSIZE }, | |
97 | }; | |
98 | ||
99 | static const struct apu_led_profile apu2_led_profile[] = { | |
100 | { "apu2:green:1", LED_ON, APU2_FCH_GPIO_BASE + 68 * APU2_IOSIZE }, | |
101 | { "apu2:green:2", LED_OFF, APU2_FCH_GPIO_BASE + 69 * APU2_IOSIZE }, | |
102 | { "apu2:green:3", LED_OFF, APU2_FCH_GPIO_BASE + 70 * APU2_IOSIZE }, | |
103 | }; | |
104 | ||
8cb21086 RDN |
105 | /* Same as apu2_led_profile, but with "3" in the LED names. */ |
106 | static const struct apu_led_profile apu3_led_profile[] = { | |
107 | { "apu3:green:1", LED_ON, APU2_FCH_GPIO_BASE + 68 * APU2_IOSIZE }, | |
108 | { "apu3:green:2", LED_OFF, APU2_FCH_GPIO_BASE + 69 * APU2_IOSIZE }, | |
109 | { "apu3:green:3", LED_OFF, APU2_FCH_GPIO_BASE + 70 * APU2_IOSIZE }, | |
110 | }; | |
111 | ||
3faee942 AM |
112 | static const struct dmi_system_id apu_led_dmi_table[] __initconst = { |
113 | { | |
114 | .ident = "apu", | |
115 | .matches = { | |
116 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), | |
117 | DMI_MATCH(DMI_PRODUCT_NAME, "APU") | |
118 | } | |
119 | }, | |
2dd1ea5b | 120 | /* PC Engines APU2 with "Legacy" bios < 4.0.8 */ |
3faee942 AM |
121 | { |
122 | .ident = "apu2", | |
123 | .matches = { | |
124 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), | |
125 | DMI_MATCH(DMI_BOARD_NAME, "APU2") | |
126 | } | |
127 | }, | |
2dd1ea5b TR |
128 | /* PC Engines APU2 with "Legacy" bios >= 4.0.8 */ |
129 | { | |
130 | .ident = "apu2", | |
131 | .matches = { | |
132 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), | |
133 | DMI_MATCH(DMI_BOARD_NAME, "apu2") | |
134 | } | |
135 | }, | |
136 | /* PC Engines APU2 with "Mainline" bios */ | |
137 | { | |
138 | .ident = "apu2", | |
139 | .matches = { | |
140 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), | |
141 | DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu2") | |
142 | } | |
143 | }, | |
8cb21086 RDN |
144 | /* PC Engines APU3 with "Legacy" bios < 4.0.8 */ |
145 | { | |
146 | .ident = "apu3", | |
147 | .matches = { | |
148 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), | |
149 | DMI_MATCH(DMI_BOARD_NAME, "APU3") | |
150 | } | |
151 | }, | |
152 | /* PC Engines APU3 with "Legacy" bios >= 4.0.8 */ | |
153 | { | |
154 | .ident = "apu3", | |
155 | .matches = { | |
156 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), | |
157 | DMI_MATCH(DMI_BOARD_NAME, "apu3") | |
158 | } | |
159 | }, | |
160 | /* PC Engines APU2 with "Mainline" bios */ | |
161 | { | |
162 | .ident = "apu3", | |
163 | .matches = { | |
164 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), | |
165 | DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu3") | |
166 | } | |
167 | }, | |
3faee942 AM |
168 | {} |
169 | }; | |
170 | MODULE_DEVICE_TABLE(dmi, apu_led_dmi_table); | |
171 | ||
172 | static void apu1_led_brightness_set(struct led_classdev *led, enum led_brightness value) | |
173 | { | |
174 | struct apu_led_priv *pled = cdev_to_priv(led); | |
175 | ||
176 | spin_lock(&apu_led->lock); | |
177 | iowrite8(value ? APU1_LEDON : APU1_LEDOFF, pled->param.addr); | |
178 | spin_unlock(&apu_led->lock); | |
179 | } | |
180 | ||
181 | static void apu2_led_brightness_set(struct led_classdev *led, enum led_brightness value) | |
182 | { | |
183 | struct apu_led_priv *pled = cdev_to_priv(led); | |
184 | u32 value_new; | |
185 | ||
186 | spin_lock(&apu_led->lock); | |
187 | ||
188 | value_new = ioread32(pled->param.addr); | |
189 | ||
190 | if (value) | |
191 | value_new &= ~BIT(APU2_GPIO_BIT_WRITE); | |
192 | else | |
193 | value_new |= BIT(APU2_GPIO_BIT_WRITE); | |
194 | ||
195 | iowrite32(value_new, pled->param.addr); | |
196 | ||
197 | spin_unlock(&apu_led->lock); | |
198 | } | |
199 | ||
200 | static int apu_led_config(struct device *dev, struct apu_led_pdata *apuld) | |
201 | { | |
202 | int i; | |
203 | int err; | |
204 | ||
a86854d0 KC |
205 | apu_led->pled = devm_kcalloc(dev, |
206 | apu_led->num_led_instances, sizeof(struct apu_led_priv), | |
3faee942 AM |
207 | GFP_KERNEL); |
208 | ||
209 | if (!apu_led->pled) | |
210 | return -ENOMEM; | |
211 | ||
212 | for (i = 0; i < apu_led->num_led_instances; i++) { | |
213 | struct apu_led_priv *pled = &apu_led->pled[i]; | |
214 | struct led_classdev *led_cdev = &pled->cdev; | |
215 | ||
216 | led_cdev->name = apu_led->profile[i].name; | |
217 | led_cdev->brightness = apu_led->profile[i].brightness; | |
218 | led_cdev->max_brightness = 1; | |
219 | led_cdev->flags = LED_CORE_SUSPENDRESUME; | |
220 | if (apu_led->platform == APU1_LED_PLATFORM) | |
221 | led_cdev->brightness_set = apu1_led_brightness_set; | |
222 | else if (apu_led->platform == APU2_LED_PLATFORM) | |
223 | led_cdev->brightness_set = apu2_led_brightness_set; | |
224 | ||
225 | pled->param.addr = devm_ioremap(dev, | |
226 | apu_led->profile[i].offset, apu_led->iosize); | |
227 | if (!pled->param.addr) { | |
228 | err = -ENOMEM; | |
229 | goto error; | |
230 | } | |
231 | ||
232 | err = led_classdev_register(dev, led_cdev); | |
233 | if (err) | |
234 | goto error; | |
235 | ||
236 | led_cdev->brightness_set(led_cdev, apu_led->profile[i].brightness); | |
237 | } | |
238 | ||
239 | return 0; | |
240 | ||
241 | error: | |
242 | while (i-- > 0) | |
243 | led_classdev_unregister(&apu_led->pled[i].cdev); | |
244 | ||
245 | return err; | |
246 | } | |
247 | ||
248 | static int __init apu_led_probe(struct platform_device *pdev) | |
249 | { | |
250 | apu_led = devm_kzalloc(&pdev->dev, sizeof(*apu_led), GFP_KERNEL); | |
251 | ||
252 | if (!apu_led) | |
253 | return -ENOMEM; | |
254 | ||
255 | apu_led->pdev = pdev; | |
256 | ||
92d7ec1d | 257 | if (dmi_match(DMI_PRODUCT_NAME, "APU")) { |
3faee942 AM |
258 | apu_led->profile = apu1_led_profile; |
259 | apu_led->platform = APU1_LED_PLATFORM; | |
260 | apu_led->num_led_instances = ARRAY_SIZE(apu1_led_profile); | |
261 | apu_led->iosize = APU1_IOSIZE; | |
2dd1ea5b TR |
262 | } else if (dmi_match(DMI_BOARD_NAME, "APU2") || |
263 | dmi_match(DMI_BOARD_NAME, "apu2") || | |
264 | dmi_match(DMI_BOARD_NAME, "PC Engines apu2")) { | |
3faee942 AM |
265 | apu_led->profile = apu2_led_profile; |
266 | apu_led->platform = APU2_LED_PLATFORM; | |
267 | apu_led->num_led_instances = ARRAY_SIZE(apu2_led_profile); | |
268 | apu_led->iosize = APU2_IOSIZE; | |
8cb21086 RDN |
269 | } else if (dmi_match(DMI_BOARD_NAME, "APU3") || |
270 | dmi_match(DMI_BOARD_NAME, "apu3") || | |
271 | dmi_match(DMI_BOARD_NAME, "PC Engines apu3")) { | |
272 | apu_led->profile = apu3_led_profile; | |
273 | /* Otherwise identical to APU2. */ | |
274 | apu_led->platform = APU2_LED_PLATFORM; | |
275 | apu_led->num_led_instances = ARRAY_SIZE(apu3_led_profile); | |
276 | apu_led->iosize = APU2_IOSIZE; | |
3faee942 AM |
277 | } |
278 | ||
279 | spin_lock_init(&apu_led->lock); | |
280 | return apu_led_config(&pdev->dev, apu_led); | |
281 | } | |
282 | ||
283 | static struct platform_driver apu_led_driver = { | |
284 | .driver = { | |
285 | .name = KBUILD_MODNAME, | |
286 | }, | |
287 | }; | |
288 | ||
289 | static int __init apu_led_init(void) | |
290 | { | |
291 | struct platform_device *pdev; | |
292 | int err; | |
293 | ||
294 | if (!dmi_match(DMI_SYS_VENDOR, "PC Engines")) { | |
295 | pr_err("No PC Engines board detected\n"); | |
296 | return -ENODEV; | |
297 | } | |
2dd1ea5b TR |
298 | if (!(dmi_match(DMI_PRODUCT_NAME, "APU") || |
299 | dmi_match(DMI_PRODUCT_NAME, "APU2") || | |
300 | dmi_match(DMI_PRODUCT_NAME, "apu2") || | |
8cb21086 RDN |
301 | dmi_match(DMI_PRODUCT_NAME, "PC Engines apu2") || |
302 | dmi_match(DMI_PRODUCT_NAME, "APU3") || | |
303 | dmi_match(DMI_PRODUCT_NAME, "apu3") || | |
304 | dmi_match(DMI_PRODUCT_NAME, "PC Engines apu3"))) { | |
3faee942 AM |
305 | pr_err("Unknown PC Engines board: %s\n", |
306 | dmi_get_system_info(DMI_PRODUCT_NAME)); | |
307 | return -ENODEV; | |
308 | } | |
309 | ||
310 | pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); | |
311 | if (IS_ERR(pdev)) { | |
312 | pr_err("Device allocation failed\n"); | |
313 | return PTR_ERR(pdev); | |
314 | } | |
315 | ||
316 | err = platform_driver_probe(&apu_led_driver, apu_led_probe); | |
317 | if (err) { | |
318 | pr_err("Probe platform driver failed\n"); | |
319 | platform_device_unregister(pdev); | |
320 | } | |
321 | ||
322 | return err; | |
323 | } | |
324 | ||
325 | static void __exit apu_led_exit(void) | |
326 | { | |
327 | int i; | |
328 | ||
329 | for (i = 0; i < apu_led->num_led_instances; i++) | |
330 | led_classdev_unregister(&apu_led->pled[i].cdev); | |
331 | ||
332 | platform_device_unregister(apu_led->pdev); | |
333 | platform_driver_unregister(&apu_led_driver); | |
334 | } | |
335 | ||
336 | module_init(apu_led_init); | |
337 | module_exit(apu_led_exit); | |
338 | ||
339 | MODULE_AUTHOR("Alan Mizrahi"); | |
340 | MODULE_DESCRIPTION("PC Engines APU family LED driver"); | |
341 | MODULE_LICENSE("GPL v2"); | |
342 | MODULE_ALIAS("platform:leds_apu"); |