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, pdata, sz);
171 	if (IS_ERR(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(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(0x530) | BIT(28), 0x530);
190 
191 	gpiod_add_lookup_table(&tink_leds_table);
192 	gpiod_add_lookup_table(&tink_keys_table);
193 
194 	tink_leds_pdev = tink_create_dev("leds-gpio",
195 		&tink_leds_pdata, sizeof(tink_leds_pdata));
196 	if (IS_ERR(tink_leds_pdev)) {
197 		ret = PTR_ERR(tink_leds_pdev);
198 		goto err;
199 	}
200 
201 	tink_keys_pdev = tink_create_dev("gpio-keys-polled",
202 		&tink_buttons_pdata, sizeof(tink_buttons_pdata));
203 	if (IS_ERR(tink_keys_pdev)) {
204 		ret = PTR_ERR(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(&tink_keys_table);
213 	gpiod_remove_lookup_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(&tink_keys_table);
223 	gpiod_remove_lookup_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