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 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
35 | |
36 | #include <linux/dmi.h> |
37 | #include <linux/err.h> |
38 | #include <linux/init.h> |
39 | #include <linux/io.h> |
40 | #include <linux/kernel.h> |
41 | #include <linux/leds.h> |
42 | #include <linux/module.h> |
43 | #include <linux/platform_device.h> |
44 | |
45 | #define APU1_FCH_ACPI_MMIO_BASE 0xFED80000 |
46 | #define APU1_FCH_GPIO_BASE (APU1_FCH_ACPI_MMIO_BASE + 0x01BD) |
47 | #define APU1_LEDON 0x08 |
48 | #define APU1_LEDOFF 0xC8 |
49 | #define APU1_NUM_GPIO 3 |
50 | #define APU1_IOSIZE sizeof(u8) |
51 | |
52 | /* LED access parameters */ |
53 | struct apu_param { |
54 | void __iomem *addr; /* for ioread/iowrite */ |
55 | }; |
56 | |
57 | /* LED private data */ |
58 | struct apu_led_priv { |
59 | struct led_classdev cdev; |
60 | struct apu_param param; |
61 | }; |
62 | #define cdev_to_priv(c) container_of(c, struct apu_led_priv, cdev) |
63 | |
64 | /* LED profile */ |
65 | struct apu_led_profile { |
66 | const char *name; |
67 | enum led_brightness brightness; |
68 | unsigned long offset; /* for devm_ioremap */ |
69 | }; |
70 | |
71 | struct apu_led_pdata { |
72 | struct platform_device *pdev; |
73 | struct apu_led_priv *pled; |
74 | spinlock_t lock; |
75 | }; |
76 | |
77 | static struct apu_led_pdata *apu_led; |
78 | |
79 | static const struct apu_led_profile apu1_led_profile[] = { |
80 | { "apu:green:1" , LED_ON, APU1_FCH_GPIO_BASE + 0 * APU1_IOSIZE }, |
81 | { "apu:green:2" , LED_OFF, APU1_FCH_GPIO_BASE + 1 * APU1_IOSIZE }, |
82 | { "apu:green:3" , LED_OFF, APU1_FCH_GPIO_BASE + 2 * APU1_IOSIZE }, |
83 | }; |
84 | |
85 | static const struct dmi_system_id apu_led_dmi_table[] __initconst = { |
86 | /* PC Engines APU with factory bios "SageBios_PCEngines_APU-45" */ |
87 | { |
88 | .ident = "apu" , |
89 | .matches = { |
90 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
91 | DMI_MATCH(DMI_PRODUCT_NAME, "APU" ) |
92 | } |
93 | }, |
94 | /* PC Engines APU with "Mainline" bios >= 4.6.8 */ |
95 | { |
96 | .ident = "apu" , |
97 | .matches = { |
98 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
99 | DMI_MATCH(DMI_PRODUCT_NAME, "apu1" ) |
100 | } |
101 | }, |
102 | {} |
103 | }; |
104 | MODULE_DEVICE_TABLE(dmi, apu_led_dmi_table); |
105 | |
106 | static void apu1_led_brightness_set(struct led_classdev *led, enum led_brightness value) |
107 | { |
108 | struct apu_led_priv *pled = cdev_to_priv(led); |
109 | |
110 | spin_lock(lock: &apu_led->lock); |
111 | iowrite8(value ? APU1_LEDON : APU1_LEDOFF, pled->param.addr); |
112 | spin_unlock(lock: &apu_led->lock); |
113 | } |
114 | |
115 | static int apu_led_config(struct device *dev, struct apu_led_pdata *apuld) |
116 | { |
117 | int i; |
118 | int err; |
119 | |
120 | apu_led->pled = devm_kcalloc(dev, |
121 | ARRAY_SIZE(apu1_led_profile), size: sizeof(struct apu_led_priv), |
122 | GFP_KERNEL); |
123 | |
124 | if (!apu_led->pled) |
125 | return -ENOMEM; |
126 | |
127 | for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++) { |
128 | struct apu_led_priv *pled = &apu_led->pled[i]; |
129 | struct led_classdev *led_cdev = &pled->cdev; |
130 | |
131 | led_cdev->name = apu1_led_profile[i].name; |
132 | led_cdev->brightness = apu1_led_profile[i].brightness; |
133 | led_cdev->max_brightness = 1; |
134 | led_cdev->flags = LED_CORE_SUSPENDRESUME; |
135 | led_cdev->brightness_set = apu1_led_brightness_set; |
136 | |
137 | pled->param.addr = devm_ioremap(dev, |
138 | offset: apu1_led_profile[i].offset, APU1_IOSIZE); |
139 | if (!pled->param.addr) { |
140 | err = -ENOMEM; |
141 | goto error; |
142 | } |
143 | |
144 | err = led_classdev_register(parent: dev, led_cdev); |
145 | if (err) |
146 | goto error; |
147 | |
148 | apu1_led_brightness_set(led: led_cdev, value: apu1_led_profile[i].brightness); |
149 | } |
150 | |
151 | return 0; |
152 | |
153 | error: |
154 | while (i-- > 0) |
155 | led_classdev_unregister(led_cdev: &apu_led->pled[i].cdev); |
156 | |
157 | return err; |
158 | } |
159 | |
160 | static int __init apu_led_probe(struct platform_device *pdev) |
161 | { |
162 | apu_led = devm_kzalloc(dev: &pdev->dev, size: sizeof(*apu_led), GFP_KERNEL); |
163 | |
164 | if (!apu_led) |
165 | return -ENOMEM; |
166 | |
167 | apu_led->pdev = pdev; |
168 | |
169 | spin_lock_init(&apu_led->lock); |
170 | return apu_led_config(dev: &pdev->dev, apuld: apu_led); |
171 | } |
172 | |
173 | static struct platform_driver apu_led_driver = { |
174 | .driver = { |
175 | .name = KBUILD_MODNAME, |
176 | }, |
177 | }; |
178 | |
179 | static int __init apu_led_init(void) |
180 | { |
181 | struct platform_device *pdev; |
182 | int err; |
183 | |
184 | if (!(dmi_match(f: DMI_SYS_VENDOR, str: "PC Engines" ) && |
185 | (dmi_match(f: DMI_PRODUCT_NAME, str: "APU" ) || dmi_match(f: DMI_PRODUCT_NAME, str: "apu1" )))) { |
186 | pr_err("No PC Engines APUv1 board detected. For APUv2,3 support, enable CONFIG_PCENGINES_APU2\n" ); |
187 | return -ENODEV; |
188 | } |
189 | |
190 | pdev = platform_device_register_simple(KBUILD_MODNAME, id: -1, NULL, num: 0); |
191 | if (IS_ERR(ptr: pdev)) { |
192 | pr_err("Device allocation failed\n" ); |
193 | return PTR_ERR(ptr: pdev); |
194 | } |
195 | |
196 | err = platform_driver_probe(&apu_led_driver, apu_led_probe); |
197 | if (err) { |
198 | pr_err("Probe platform driver failed\n" ); |
199 | platform_device_unregister(pdev); |
200 | } |
201 | |
202 | return err; |
203 | } |
204 | |
205 | static void __exit apu_led_exit(void) |
206 | { |
207 | int i; |
208 | |
209 | for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++) |
210 | led_classdev_unregister(led_cdev: &apu_led->pled[i].cdev); |
211 | |
212 | platform_device_unregister(apu_led->pdev); |
213 | platform_driver_unregister(&apu_led_driver); |
214 | } |
215 | |
216 | module_init(apu_led_init); |
217 | module_exit(apu_led_exit); |
218 | |
219 | MODULE_AUTHOR("Alan Mizrahi" ); |
220 | MODULE_DESCRIPTION("PC Engines APU1 front LED driver" ); |
221 | MODULE_LICENSE("GPL v2" ); |
222 | MODULE_ALIAS("platform:leds_apu" ); |
223 | |