aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Mizrahi <alan@mizrahi.com.ve>2017-11-02 21:38:07 -0400
committerJacek Anaszewski <jacek.anaszewski@gmail.com>2017-11-06 16:05:00 -0500
commit3faee9423ce07186fc9dcec2981d4eb8af8872bb (patch)
treee3ff25d592377080fb267d2a55163128bdc80051
parentf2ea85d760fbfb8f9f81c7309c6e361119ce7a32 (diff)
leds: Add driver for PC Engines APU/APU2 LEDs
This patch implements the driver to support the front panel LEDs for PC Engines APU and APU2 boards. Signed-off-by: Alan Mizrahi <alan@mizrahi.com.ve> Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
-rw-r--r--drivers/leds/Kconfig10
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-apu.c278
3 files changed, 289 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 52ea34e337cd..318a28fd58fe 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -57,6 +57,16 @@ config LEDS_AAT1290
57 depends on PINCTRL 57 depends on PINCTRL
58 help 58 help
59 This option enables support for the LEDs on the AAT1290. 59 This option enables support for the LEDs on the AAT1290.
60config LEDS_APU
61 tristate "Front panel LED support for PC Engines APU/APU2 boards"
62 depends on LEDS_CLASS
63 depends on X86 && DMI
64 help
65 This driver makes the PC Engines APU/APU2 front panel LEDs
66 accessible from userspace programs through the LED subsystem.
67
68 To compile this driver as a module, choose M here: the
69 module will be called leds-apu.
60 70
61config LEDS_AS3645A 71config LEDS_AS3645A
62 tristate "AS3645A LED flash controller support" 72 tristate "AS3645A LED flash controller support"
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7d7b26552923..6de49790255e 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
8# LED Platform Drivers 8# LED Platform Drivers
9obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o 9obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
10obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o 10obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
11obj-$(CONFIG_LEDS_APU) += leds-apu.o
11obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o 12obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o
12obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o 13obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
13obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o 14obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
diff --git a/drivers/leds/leds-apu.c b/drivers/leds/leds-apu.c
new file mode 100644
index 000000000000..74820aab9497
--- /dev/null
+++ b/drivers/leds/leds-apu.c
@@ -0,0 +1,278 @@
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 */
57struct apu_param {
58 void __iomem *addr; /* for ioread/iowrite */
59};
60
61/* LED private data */
62struct 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 */
69struct 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 */
76enum apu_led_platform_types {
77 APU1_LED_PLATFORM,
78 APU2_LED_PLATFORM,
79};
80
81struct 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
91static struct apu_led_pdata *apu_led;
92
93static 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
99static 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
105static const struct dmi_system_id apu_led_dmi_table[] __initconst = {
106 {
107 .ident = "apu",
108 .matches = {
109 DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
110 DMI_MATCH(DMI_PRODUCT_NAME, "APU")
111 }
112 },
113 {
114 .ident = "apu2",
115 .matches = {
116 DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
117 DMI_MATCH(DMI_BOARD_NAME, "APU2")
118 }
119 },
120 {}
121};
122MODULE_DEVICE_TABLE(dmi, apu_led_dmi_table);
123
124static void apu1_led_brightness_set(struct led_classdev *led, enum led_brightness value)
125{
126 struct apu_led_priv *pled = cdev_to_priv(led);
127
128 spin_lock(&apu_led->lock);
129 iowrite8(value ? APU1_LEDON : APU1_LEDOFF, pled->param.addr);
130 spin_unlock(&apu_led->lock);
131}
132
133static void apu2_led_brightness_set(struct led_classdev *led, enum led_brightness value)
134{
135 struct apu_led_priv *pled = cdev_to_priv(led);
136 u32 value_new;
137
138 spin_lock(&apu_led->lock);
139
140 value_new = ioread32(pled->param.addr);
141
142 if (value)
143 value_new &= ~BIT(APU2_GPIO_BIT_WRITE);
144 else
145 value_new |= BIT(APU2_GPIO_BIT_WRITE);
146
147 iowrite32(value_new, pled->param.addr);
148
149 spin_unlock(&apu_led->lock);
150}
151
152static int apu_led_config(struct device *dev, struct apu_led_pdata *apuld)
153{
154 int i;
155 int err;
156
157 apu_led->pled = devm_kzalloc(dev,
158 sizeof(struct apu_led_priv) * apu_led->num_led_instances,
159 GFP_KERNEL);
160
161 if (!apu_led->pled)
162 return -ENOMEM;
163
164 for (i = 0; i < apu_led->num_led_instances; i++) {
165 struct apu_led_priv *pled = &apu_led->pled[i];
166 struct led_classdev *led_cdev = &pled->cdev;
167
168 led_cdev->name = apu_led->profile[i].name;
169 led_cdev->brightness = apu_led->profile[i].brightness;
170 led_cdev->max_brightness = 1;
171 led_cdev->flags = LED_CORE_SUSPENDRESUME;
172 if (apu_led->platform == APU1_LED_PLATFORM)
173 led_cdev->brightness_set = apu1_led_brightness_set;
174 else if (apu_led->platform == APU2_LED_PLATFORM)
175 led_cdev->brightness_set = apu2_led_brightness_set;
176
177 pled->param.addr = devm_ioremap(dev,
178 apu_led->profile[i].offset, apu_led->iosize);
179 if (!pled->param.addr) {
180 err = -ENOMEM;
181 goto error;
182 }
183
184 err = led_classdev_register(dev, led_cdev);
185 if (err)
186 goto error;
187
188 led_cdev->brightness_set(led_cdev, apu_led->profile[i].brightness);
189 }
190
191 return 0;
192
193error:
194 while (i-- > 0)
195 led_classdev_unregister(&apu_led->pled[i].cdev);
196
197 return err;
198}
199
200static int __init apu_led_probe(struct platform_device *pdev)
201{
202 apu_led = devm_kzalloc(&pdev->dev, sizeof(*apu_led), GFP_KERNEL);
203
204 if (!apu_led)
205 return -ENOMEM;
206
207 apu_led->pdev = pdev;
208
209 if (dmi_match(DMI_BOARD_NAME, "APU")) {
210 apu_led->profile = apu1_led_profile;
211 apu_led->platform = APU1_LED_PLATFORM;
212 apu_led->num_led_instances = ARRAY_SIZE(apu1_led_profile);
213 apu_led->iosize = APU1_IOSIZE;
214 } else if (dmi_match(DMI_BOARD_NAME, "APU2")) {
215 apu_led->profile = apu2_led_profile;
216 apu_led->platform = APU2_LED_PLATFORM;
217 apu_led->num_led_instances = ARRAY_SIZE(apu2_led_profile);
218 apu_led->iosize = APU2_IOSIZE;
219 }
220
221 spin_lock_init(&apu_led->lock);
222 return apu_led_config(&pdev->dev, apu_led);
223}
224
225static struct platform_driver apu_led_driver = {
226 .driver = {
227 .name = KBUILD_MODNAME,
228 },
229};
230
231static int __init apu_led_init(void)
232{
233 struct platform_device *pdev;
234 int err;
235
236 if (!dmi_match(DMI_SYS_VENDOR, "PC Engines")) {
237 pr_err("No PC Engines board detected\n");
238 return -ENODEV;
239 }
240 if (!(dmi_match(DMI_PRODUCT_NAME, "APU") || dmi_match(DMI_PRODUCT_NAME, "APU2"))) {
241 pr_err("Unknown PC Engines board: %s\n",
242 dmi_get_system_info(DMI_PRODUCT_NAME));
243 return -ENODEV;
244 }
245
246 pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
247 if (IS_ERR(pdev)) {
248 pr_err("Device allocation failed\n");
249 return PTR_ERR(pdev);
250 }
251
252 err = platform_driver_probe(&apu_led_driver, apu_led_probe);
253 if (err) {
254 pr_err("Probe platform driver failed\n");
255 platform_device_unregister(pdev);
256 }
257
258 return err;
259}
260
261static void __exit apu_led_exit(void)
262{
263 int i;
264
265 for (i = 0; i < apu_led->num_led_instances; i++)
266 led_classdev_unregister(&apu_led->pled[i].cdev);
267
268 platform_device_unregister(apu_led->pdev);
269 platform_driver_unregister(&apu_led_driver);
270}
271
272module_init(apu_led_init);
273module_exit(apu_led_exit);
274
275MODULE_AUTHOR("Alan Mizrahi");
276MODULE_DESCRIPTION("PC Engines APU family LED driver");
277MODULE_LICENSE("GPL v2");
278MODULE_ALIAS("platform:leds_apu");