1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * Cisco Meraki MX100 (Tinkerbell) board platform driver |
5 | * |
6 | * Based off of arch/x86/platform/meraki/tink.c from the |
7 | * Meraki GPL release meraki-firmware-sources-r23-20150601 |
8 | * |
9 | * Format inspired by platform/x86/pcengines-apuv2.c |
10 | * |
11 | * Copyright (C) 2021 Chris Blake <chrisrblake93@gmail.com> |
12 | */ |
13 | |
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
15 | |
16 | #include <linux/dmi.h> |
17 | #include <linux/err.h> |
18 | #include <linux/gpio_keys.h> |
19 | #include <linux/gpio/machine.h> |
20 | #include <linux/input.h> |
21 | #include <linux/io.h> |
22 | #include <linux/kernel.h> |
23 | #include <linux/leds.h> |
24 | #include <linux/module.h> |
25 | #include <linux/platform_device.h> |
26 | |
27 | #define TINK_GPIO_DRIVER_NAME "gpio_ich" |
28 | |
29 | /* LEDs */ |
30 | static const struct gpio_led tink_leds[] = { |
31 | { |
32 | .name = "mx100:green:internet" , |
33 | .default_trigger = "default-on" , |
34 | }, |
35 | { |
36 | .name = "mx100:green:lan2" , |
37 | }, |
38 | { |
39 | .name = "mx100:green:lan3" , |
40 | }, |
41 | { |
42 | .name = "mx100:green:lan4" , |
43 | }, |
44 | { |
45 | .name = "mx100:green:lan5" , |
46 | }, |
47 | { |
48 | .name = "mx100:green:lan6" , |
49 | }, |
50 | { |
51 | .name = "mx100:green:lan7" , |
52 | }, |
53 | { |
54 | .name = "mx100:green:lan8" , |
55 | }, |
56 | { |
57 | .name = "mx100:green:lan9" , |
58 | }, |
59 | { |
60 | .name = "mx100:green:lan10" , |
61 | }, |
62 | { |
63 | .name = "mx100:green:lan11" , |
64 | }, |
65 | { |
66 | .name = "mx100:green:ha" , |
67 | }, |
68 | { |
69 | .name = "mx100:orange:ha" , |
70 | }, |
71 | { |
72 | .name = "mx100:green:usb" , |
73 | }, |
74 | { |
75 | .name = "mx100:orange:usb" , |
76 | }, |
77 | }; |
78 | |
79 | static const struct gpio_led_platform_data tink_leds_pdata = { |
80 | .num_leds = ARRAY_SIZE(tink_leds), |
81 | .leds = tink_leds, |
82 | }; |
83 | |
84 | static struct gpiod_lookup_table tink_leds_table = { |
85 | .dev_id = "leds-gpio" , |
86 | .table = { |
87 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 11, |
88 | NULL, 0, GPIO_ACTIVE_LOW), |
89 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 18, |
90 | NULL, 1, GPIO_ACTIVE_HIGH), |
91 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 20, |
92 | NULL, 2, GPIO_ACTIVE_HIGH), |
93 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 22, |
94 | NULL, 3, GPIO_ACTIVE_HIGH), |
95 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 23, |
96 | NULL, 4, GPIO_ACTIVE_HIGH), |
97 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 32, |
98 | NULL, 5, GPIO_ACTIVE_HIGH), |
99 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 34, |
100 | NULL, 6, GPIO_ACTIVE_HIGH), |
101 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 35, |
102 | NULL, 7, GPIO_ACTIVE_HIGH), |
103 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 36, |
104 | NULL, 8, GPIO_ACTIVE_HIGH), |
105 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 37, |
106 | NULL, 9, GPIO_ACTIVE_HIGH), |
107 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 48, |
108 | NULL, 10, GPIO_ACTIVE_HIGH), |
109 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 16, |
110 | NULL, 11, GPIO_ACTIVE_LOW), |
111 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 7, |
112 | NULL, 12, GPIO_ACTIVE_LOW), |
113 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 21, |
114 | NULL, 13, GPIO_ACTIVE_LOW), |
115 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 19, |
116 | NULL, 14, GPIO_ACTIVE_LOW), |
117 | {} /* Terminating entry */ |
118 | } |
119 | }; |
120 | |
121 | /* Reset Button */ |
122 | static struct gpio_keys_button tink_buttons[] = { |
123 | { |
124 | .desc = "Reset" , |
125 | .type = EV_KEY, |
126 | .code = KEY_RESTART, |
127 | .active_low = 1, |
128 | .debounce_interval = 100, |
129 | }, |
130 | }; |
131 | |
132 | static const struct gpio_keys_platform_data tink_buttons_pdata = { |
133 | .buttons = tink_buttons, |
134 | .nbuttons = ARRAY_SIZE(tink_buttons), |
135 | .poll_interval = 20, |
136 | .rep = 0, |
137 | .name = "mx100-keys" , |
138 | }; |
139 | |
140 | static struct gpiod_lookup_table tink_keys_table = { |
141 | .dev_id = "gpio-keys-polled" , |
142 | .table = { |
143 | GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 60, |
144 | NULL, 0, GPIO_ACTIVE_LOW), |
145 | {} /* Terminating entry */ |
146 | } |
147 | }; |
148 | |
149 | /* Board setup */ |
150 | static const struct dmi_system_id tink_systems[] __initconst = { |
151 | { |
152 | .matches = { |
153 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Cisco" ), |
154 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MX100-HW" ), |
155 | }, |
156 | }, |
157 | {} /* Terminating entry */ |
158 | }; |
159 | MODULE_DEVICE_TABLE(dmi, tink_systems); |
160 | |
161 | static struct platform_device *tink_leds_pdev; |
162 | static struct platform_device *tink_keys_pdev; |
163 | |
164 | static struct platform_device * __init tink_create_dev( |
165 | const char *name, const void *pdata, size_t sz) |
166 | { |
167 | struct platform_device *pdev; |
168 | |
169 | pdev = platform_device_register_data(NULL, |
170 | name, PLATFORM_DEVID_NONE, data: pdata, size: sz); |
171 | if (IS_ERR(ptr: pdev)) |
172 | pr_err("failed registering %s: %ld\n" , name, PTR_ERR(pdev)); |
173 | |
174 | return pdev; |
175 | } |
176 | |
177 | static int __init tink_board_init(void) |
178 | { |
179 | int ret; |
180 | |
181 | if (!dmi_first_match(list: tink_systems)) |
182 | return -ENODEV; |
183 | |
184 | /* |
185 | * We need to make sure that GPIO60 isn't set to native mode as is default since it's our |
186 | * Reset Button. To do this, write to GPIO_USE_SEL2 to have GPIO60 set to GPIO mode. |
187 | * This is documented on page 1609 of the PCH datasheet, order number 327879-005US |
188 | */ |
189 | outl(inl(port: 0x530) | BIT(28), port: 0x530); |
190 | |
191 | gpiod_add_lookup_table(table: &tink_leds_table); |
192 | gpiod_add_lookup_table(table: &tink_keys_table); |
193 | |
194 | tink_leds_pdev = tink_create_dev(name: "leds-gpio" , |
195 | pdata: &tink_leds_pdata, sz: sizeof(tink_leds_pdata)); |
196 | if (IS_ERR(ptr: tink_leds_pdev)) { |
197 | ret = PTR_ERR(ptr: tink_leds_pdev); |
198 | goto err; |
199 | } |
200 | |
201 | tink_keys_pdev = tink_create_dev(name: "gpio-keys-polled" , |
202 | pdata: &tink_buttons_pdata, sz: sizeof(tink_buttons_pdata)); |
203 | if (IS_ERR(ptr: tink_keys_pdev)) { |
204 | ret = PTR_ERR(ptr: tink_keys_pdev); |
205 | platform_device_unregister(tink_leds_pdev); |
206 | goto err; |
207 | } |
208 | |
209 | return 0; |
210 | |
211 | err: |
212 | gpiod_remove_lookup_table(table: &tink_keys_table); |
213 | gpiod_remove_lookup_table(table: &tink_leds_table); |
214 | return ret; |
215 | } |
216 | module_init(tink_board_init); |
217 | |
218 | static void __exit tink_board_exit(void) |
219 | { |
220 | platform_device_unregister(tink_keys_pdev); |
221 | platform_device_unregister(tink_leds_pdev); |
222 | gpiod_remove_lookup_table(table: &tink_keys_table); |
223 | gpiod_remove_lookup_table(table: &tink_leds_table); |
224 | } |
225 | module_exit(tink_board_exit); |
226 | |
227 | MODULE_AUTHOR("Chris Blake <chrisrblake93@gmail.com>" ); |
228 | MODULE_DESCRIPTION("Cisco Meraki MX100 Platform Driver" ); |
229 | MODULE_LICENSE("GPL" ); |
230 | MODULE_ALIAS("platform:meraki-mx100" ); |
231 | |