1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * PC-Engines APUv2/APUv3 board platform driver |
5 | * for GPIO buttons and LEDs |
6 | * |
7 | * Copyright (C) 2018 metux IT consult |
8 | * Author: Enrico Weigelt <info@metux.net> |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | |
13 | #include <linux/dmi.h> |
14 | #include <linux/err.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/leds.h> |
17 | #include <linux/module.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/gpio_keys.h> |
20 | #include <linux/gpio/machine.h> |
21 | #include <linux/input.h> |
22 | #include <linux/platform_data/gpio/gpio-amd-fch.h> |
23 | |
24 | /* |
25 | * NOTE: this driver only supports APUv2/3 - not APUv1, as this one |
26 | * has completely different register layouts. |
27 | */ |
28 | |
29 | /* Register mappings */ |
30 | #define APU2_GPIO_REG_LED1 AMD_FCH_GPIO_REG_GPIO57 |
31 | #define APU2_GPIO_REG_LED2 AMD_FCH_GPIO_REG_GPIO58 |
32 | #define APU2_GPIO_REG_LED3 AMD_FCH_GPIO_REG_GPIO59_DEVSLP1 |
33 | #define APU2_GPIO_REG_MODESW AMD_FCH_GPIO_REG_GPIO32_GE1 |
34 | #define APU2_GPIO_REG_SIMSWAP AMD_FCH_GPIO_REG_GPIO33_GE2 |
35 | #define APU2_GPIO_REG_MPCIE2 AMD_FCH_GPIO_REG_GPIO55_DEVSLP0 |
36 | #define APU2_GPIO_REG_MPCIE3 AMD_FCH_GPIO_REG_GPIO51 |
37 | |
38 | /* Order in which the GPIO lines are defined in the register list */ |
39 | #define APU2_GPIO_LINE_LED1 0 |
40 | #define APU2_GPIO_LINE_LED2 1 |
41 | #define APU2_GPIO_LINE_LED3 2 |
42 | #define APU2_GPIO_LINE_MODESW 3 |
43 | #define APU2_GPIO_LINE_SIMSWAP 4 |
44 | #define APU2_GPIO_LINE_MPCIE2 5 |
45 | #define APU2_GPIO_LINE_MPCIE3 6 |
46 | |
47 | /* GPIO device */ |
48 | |
49 | static int apu2_gpio_regs[] = { |
50 | [APU2_GPIO_LINE_LED1] = APU2_GPIO_REG_LED1, |
51 | [APU2_GPIO_LINE_LED2] = APU2_GPIO_REG_LED2, |
52 | [APU2_GPIO_LINE_LED3] = APU2_GPIO_REG_LED3, |
53 | [APU2_GPIO_LINE_MODESW] = APU2_GPIO_REG_MODESW, |
54 | [APU2_GPIO_LINE_SIMSWAP] = APU2_GPIO_REG_SIMSWAP, |
55 | [APU2_GPIO_LINE_MPCIE2] = APU2_GPIO_REG_MPCIE2, |
56 | [APU2_GPIO_LINE_MPCIE3] = APU2_GPIO_REG_MPCIE3, |
57 | }; |
58 | |
59 | static const char * const apu2_gpio_names[] = { |
60 | [APU2_GPIO_LINE_LED1] = "front-led1" , |
61 | [APU2_GPIO_LINE_LED2] = "front-led2" , |
62 | [APU2_GPIO_LINE_LED3] = "front-led3" , |
63 | [APU2_GPIO_LINE_MODESW] = "front-button" , |
64 | [APU2_GPIO_LINE_SIMSWAP] = "simswap" , |
65 | [APU2_GPIO_LINE_MPCIE2] = "mpcie2_reset" , |
66 | [APU2_GPIO_LINE_MPCIE3] = "mpcie3_reset" , |
67 | }; |
68 | |
69 | static const struct amd_fch_gpio_pdata board_apu2 = { |
70 | .gpio_num = ARRAY_SIZE(apu2_gpio_regs), |
71 | .gpio_reg = apu2_gpio_regs, |
72 | .gpio_names = apu2_gpio_names, |
73 | }; |
74 | |
75 | /* GPIO LEDs device */ |
76 | |
77 | static const struct gpio_led apu2_leds[] = { |
78 | { .name = "apu:green:1" }, |
79 | { .name = "apu:green:2" }, |
80 | { .name = "apu:green:3" }, |
81 | }; |
82 | |
83 | static const struct gpio_led_platform_data apu2_leds_pdata = { |
84 | .num_leds = ARRAY_SIZE(apu2_leds), |
85 | .leds = apu2_leds, |
86 | }; |
87 | |
88 | static struct gpiod_lookup_table gpios_led_table = { |
89 | .dev_id = "leds-gpio" , |
90 | .table = { |
91 | GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1, |
92 | NULL, 0, GPIO_ACTIVE_LOW), |
93 | GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED2, |
94 | NULL, 1, GPIO_ACTIVE_LOW), |
95 | GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED3, |
96 | NULL, 2, GPIO_ACTIVE_LOW), |
97 | {} /* Terminating entry */ |
98 | } |
99 | }; |
100 | |
101 | /* GPIO keyboard device */ |
102 | |
103 | static struct gpio_keys_button apu2_keys_buttons[] = { |
104 | { |
105 | .code = KEY_RESTART, |
106 | .active_low = 1, |
107 | .desc = "front button" , |
108 | .type = EV_KEY, |
109 | .debounce_interval = 10, |
110 | .value = 1, |
111 | }, |
112 | }; |
113 | |
114 | static const struct gpio_keys_platform_data apu2_keys_pdata = { |
115 | .buttons = apu2_keys_buttons, |
116 | .nbuttons = ARRAY_SIZE(apu2_keys_buttons), |
117 | .poll_interval = 100, |
118 | .rep = 0, |
119 | .name = "apu2-keys" , |
120 | }; |
121 | |
122 | static struct gpiod_lookup_table gpios_key_table = { |
123 | .dev_id = "gpio-keys-polled" , |
124 | .table = { |
125 | GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW, |
126 | NULL, 0, GPIO_ACTIVE_LOW), |
127 | {} /* Terminating entry */ |
128 | } |
129 | }; |
130 | |
131 | /* Board setup */ |
132 | |
133 | /* Note: matching works on string prefix, so "apu2" must come before "apu" */ |
134 | static const struct dmi_system_id apu_gpio_dmi_table[] __initconst = { |
135 | |
136 | /* APU2 w/ legacy BIOS < 4.0.8 */ |
137 | { |
138 | .ident = "apu2" , |
139 | .matches = { |
140 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
141 | DMI_MATCH(DMI_BOARD_NAME, "APU2" ) |
142 | }, |
143 | .driver_data = (void *)&board_apu2, |
144 | }, |
145 | /* APU2 w/ legacy BIOS >= 4.0.8 */ |
146 | { |
147 | .ident = "apu2" , |
148 | .matches = { |
149 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
150 | DMI_MATCH(DMI_BOARD_NAME, "apu2" ) |
151 | }, |
152 | .driver_data = (void *)&board_apu2, |
153 | }, |
154 | /* APU2 w/ mainline BIOS */ |
155 | { |
156 | .ident = "apu2" , |
157 | .matches = { |
158 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
159 | DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu2" ) |
160 | }, |
161 | .driver_data = (void *)&board_apu2, |
162 | }, |
163 | |
164 | /* APU3 w/ legacy BIOS < 4.0.8 */ |
165 | { |
166 | .ident = "apu3" , |
167 | .matches = { |
168 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
169 | DMI_MATCH(DMI_BOARD_NAME, "APU3" ) |
170 | }, |
171 | .driver_data = (void *)&board_apu2, |
172 | }, |
173 | /* APU3 w/ legacy BIOS >= 4.0.8 */ |
174 | { |
175 | .ident = "apu3" , |
176 | .matches = { |
177 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
178 | DMI_MATCH(DMI_BOARD_NAME, "apu3" ) |
179 | }, |
180 | .driver_data = (void *)&board_apu2, |
181 | }, |
182 | /* APU3 w/ mainline BIOS */ |
183 | { |
184 | .ident = "apu3" , |
185 | .matches = { |
186 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
187 | DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu3" ) |
188 | }, |
189 | .driver_data = (void *)&board_apu2, |
190 | }, |
191 | /* APU4 w/ legacy BIOS < 4.0.8 */ |
192 | { |
193 | .ident = "apu4" , |
194 | .matches = { |
195 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
196 | DMI_MATCH(DMI_BOARD_NAME, "APU4" ) |
197 | }, |
198 | .driver_data = (void *)&board_apu2, |
199 | }, |
200 | /* APU4 w/ legacy BIOS >= 4.0.8 */ |
201 | { |
202 | .ident = "apu4" , |
203 | .matches = { |
204 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
205 | DMI_MATCH(DMI_BOARD_NAME, "apu4" ) |
206 | }, |
207 | .driver_data = (void *)&board_apu2, |
208 | }, |
209 | /* APU4 w/ mainline BIOS */ |
210 | { |
211 | .ident = "apu4" , |
212 | .matches = { |
213 | DMI_MATCH(DMI_SYS_VENDOR, "PC Engines" ), |
214 | DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu4" ) |
215 | }, |
216 | .driver_data = (void *)&board_apu2, |
217 | }, |
218 | {} |
219 | }; |
220 | |
221 | static struct platform_device *apu_gpio_pdev; |
222 | static struct platform_device *apu_leds_pdev; |
223 | static struct platform_device *apu_keys_pdev; |
224 | |
225 | static struct platform_device * __init apu_create_pdev( |
226 | const char *name, |
227 | const void *pdata, |
228 | size_t sz) |
229 | { |
230 | struct platform_device *pdev; |
231 | |
232 | pdev = platform_device_register_resndata(NULL, |
233 | name, |
234 | PLATFORM_DEVID_NONE, |
235 | NULL, |
236 | num: 0, |
237 | data: pdata, |
238 | size: sz); |
239 | |
240 | if (IS_ERR(ptr: pdev)) |
241 | pr_err("failed registering %s: %ld\n" , name, PTR_ERR(pdev)); |
242 | |
243 | return pdev; |
244 | } |
245 | |
246 | static int __init apu_board_init(void) |
247 | { |
248 | const struct dmi_system_id *id; |
249 | |
250 | id = dmi_first_match(list: apu_gpio_dmi_table); |
251 | if (!id) { |
252 | pr_err("failed to detect APU board via DMI\n" ); |
253 | return -ENODEV; |
254 | } |
255 | |
256 | gpiod_add_lookup_table(table: &gpios_led_table); |
257 | gpiod_add_lookup_table(table: &gpios_key_table); |
258 | |
259 | apu_gpio_pdev = apu_create_pdev( |
260 | AMD_FCH_GPIO_DRIVER_NAME, |
261 | pdata: id->driver_data, |
262 | sz: sizeof(struct amd_fch_gpio_pdata)); |
263 | |
264 | apu_leds_pdev = apu_create_pdev( |
265 | name: "leds-gpio" , |
266 | pdata: &apu2_leds_pdata, |
267 | sz: sizeof(apu2_leds_pdata)); |
268 | |
269 | apu_keys_pdev = apu_create_pdev( |
270 | name: "gpio-keys-polled" , |
271 | pdata: &apu2_keys_pdata, |
272 | sz: sizeof(apu2_keys_pdata)); |
273 | |
274 | return 0; |
275 | } |
276 | |
277 | static void __exit apu_board_exit(void) |
278 | { |
279 | gpiod_remove_lookup_table(table: &gpios_led_table); |
280 | gpiod_remove_lookup_table(table: &gpios_key_table); |
281 | |
282 | platform_device_unregister(apu_keys_pdev); |
283 | platform_device_unregister(apu_leds_pdev); |
284 | platform_device_unregister(apu_gpio_pdev); |
285 | } |
286 | |
287 | module_init(apu_board_init); |
288 | module_exit(apu_board_exit); |
289 | |
290 | MODULE_AUTHOR("Enrico Weigelt, metux IT consult <info@metux.net>" ); |
291 | MODULE_DESCRIPTION("PC Engines APUv2/APUv3 board GPIO/LEDs/keys driver" ); |
292 | MODULE_LICENSE("GPL" ); |
293 | MODULE_DEVICE_TABLE(dmi, apu_gpio_dmi_table); |
294 | MODULE_SOFTDEP("pre: platform:" AMD_FCH_GPIO_DRIVER_NAME " platform:leds-gpio platform:gpio_keys_polled" ); |
295 | |