1*81ad5695SRobert Joslyn // SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
2*81ad5695SRobert Joslyn /*
3*81ad5695SRobert Joslyn  * Copyright 2023 Schweitzer Engineering Laboratories, Inc.
4*81ad5695SRobert Joslyn  * 2350 NE Hopkins Court, Pullman, WA 99163 USA
5*81ad5695SRobert Joslyn  *
6*81ad5695SRobert Joslyn  * Platform support for the b2093 mainboard used in SEL-3350 computers.
7*81ad5695SRobert Joslyn  * Consumes GPIO from the SoC to provide standard LED and power supply
8*81ad5695SRobert Joslyn  * devices.
9*81ad5695SRobert Joslyn  */
10*81ad5695SRobert Joslyn 
11*81ad5695SRobert Joslyn #include <linux/acpi.h>
12*81ad5695SRobert Joslyn #include <linux/gpio/consumer.h>
13*81ad5695SRobert Joslyn #include <linux/gpio/machine.h>
14*81ad5695SRobert Joslyn #include <linux/leds.h>
15*81ad5695SRobert Joslyn #include <linux/module.h>
16*81ad5695SRobert Joslyn #include <linux/platform_device.h>
17*81ad5695SRobert Joslyn #include <linux/power_supply.h>
18*81ad5695SRobert Joslyn 
19*81ad5695SRobert Joslyn /* Broxton communities */
20*81ad5695SRobert Joslyn #define BXT_NW "INT3452:01"
21*81ad5695SRobert Joslyn #define BXT_W  "INT3452:02"
22*81ad5695SRobert Joslyn #define BXT_SW "INT3452:03"
23*81ad5695SRobert Joslyn 
24*81ad5695SRobert Joslyn #define B2093_GPIO_ACPI_ID "SEL0003"
25*81ad5695SRobert Joslyn 
26*81ad5695SRobert Joslyn #define SEL_PS_A        "sel_ps_a"
27*81ad5695SRobert Joslyn #define SEL_PS_A_DETECT "sel_ps_a_detect"
28*81ad5695SRobert Joslyn #define SEL_PS_A_GOOD   "sel_ps_a_good"
29*81ad5695SRobert Joslyn #define SEL_PS_B        "sel_ps_b"
30*81ad5695SRobert Joslyn #define SEL_PS_B_DETECT "sel_ps_b_detect"
31*81ad5695SRobert Joslyn #define SEL_PS_B_GOOD   "sel_ps_b_good"
32*81ad5695SRobert Joslyn 
33*81ad5695SRobert Joslyn /* LEDs */
34*81ad5695SRobert Joslyn static const struct gpio_led sel3350_leds[] = {
35*81ad5695SRobert Joslyn 	{ .name = "sel:green:aux1" },
36*81ad5695SRobert Joslyn 	{ .name = "sel:green:aux2" },
37*81ad5695SRobert Joslyn 	{ .name = "sel:green:aux3" },
38*81ad5695SRobert Joslyn 	{ .name = "sel:green:aux4" },
39*81ad5695SRobert Joslyn 	{ .name = "sel:red:alarm" },
40*81ad5695SRobert Joslyn 	{ .name = "sel:green:enabled",
41*81ad5695SRobert Joslyn 	  .default_state = LEDS_GPIO_DEFSTATE_ON },
42*81ad5695SRobert Joslyn 	{ .name = "sel:red:aux1" },
43*81ad5695SRobert Joslyn 	{ .name = "sel:red:aux2" },
44*81ad5695SRobert Joslyn 	{ .name = "sel:red:aux3" },
45*81ad5695SRobert Joslyn 	{ .name = "sel:red:aux4" },
46*81ad5695SRobert Joslyn };
47*81ad5695SRobert Joslyn 
48*81ad5695SRobert Joslyn static const struct gpio_led_platform_data sel3350_leds_pdata = {
49*81ad5695SRobert Joslyn 	.num_leds = ARRAY_SIZE(sel3350_leds),
50*81ad5695SRobert Joslyn 	.leds = sel3350_leds,
51*81ad5695SRobert Joslyn };
52*81ad5695SRobert Joslyn 
53*81ad5695SRobert Joslyn /* Map GPIOs to LEDs */
54*81ad5695SRobert Joslyn static struct gpiod_lookup_table sel3350_leds_table = {
55*81ad5695SRobert Joslyn 	.dev_id = "leds-gpio",
56*81ad5695SRobert Joslyn 	.table = {
57*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_NW, 49, NULL, 0, GPIO_ACTIVE_HIGH),
58*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_NW, 50, NULL, 1, GPIO_ACTIVE_HIGH),
59*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_NW, 51, NULL, 2, GPIO_ACTIVE_HIGH),
60*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_NW, 52, NULL, 3, GPIO_ACTIVE_HIGH),
61*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_W,  20, NULL, 4, GPIO_ACTIVE_HIGH),
62*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_W,  21, NULL, 5, GPIO_ACTIVE_HIGH),
63*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_SW, 37, NULL, 6, GPIO_ACTIVE_HIGH),
64*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_SW, 38, NULL, 7, GPIO_ACTIVE_HIGH),
65*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_SW, 39, NULL, 8, GPIO_ACTIVE_HIGH),
66*81ad5695SRobert Joslyn 		GPIO_LOOKUP_IDX(BXT_SW, 40, NULL, 9, GPIO_ACTIVE_HIGH),
67*81ad5695SRobert Joslyn 		{},
68*81ad5695SRobert Joslyn 	}
69*81ad5695SRobert Joslyn };
70*81ad5695SRobert Joslyn 
71*81ad5695SRobert Joslyn /* Map GPIOs to power supplies */
72*81ad5695SRobert Joslyn static struct gpiod_lookup_table sel3350_gpios_table = {
73*81ad5695SRobert Joslyn 	.dev_id = B2093_GPIO_ACPI_ID ":00",
74*81ad5695SRobert Joslyn 	.table = {
75*81ad5695SRobert Joslyn 		GPIO_LOOKUP(BXT_NW, 44, SEL_PS_A_DETECT, GPIO_ACTIVE_LOW),
76*81ad5695SRobert Joslyn 		GPIO_LOOKUP(BXT_NW, 45, SEL_PS_A_GOOD,   GPIO_ACTIVE_LOW),
77*81ad5695SRobert Joslyn 		GPIO_LOOKUP(BXT_NW, 46, SEL_PS_B_DETECT, GPIO_ACTIVE_LOW),
78*81ad5695SRobert Joslyn 		GPIO_LOOKUP(BXT_NW, 47, SEL_PS_B_GOOD,   GPIO_ACTIVE_LOW),
79*81ad5695SRobert Joslyn 		{},
80*81ad5695SRobert Joslyn 	}
81*81ad5695SRobert Joslyn };
82*81ad5695SRobert Joslyn 
83*81ad5695SRobert Joslyn /* Power Supplies */
84*81ad5695SRobert Joslyn 
85*81ad5695SRobert Joslyn struct sel3350_power_cfg_data {
86*81ad5695SRobert Joslyn 	struct gpio_desc *ps_detect;
87*81ad5695SRobert Joslyn 	struct gpio_desc *ps_good;
88*81ad5695SRobert Joslyn };
89*81ad5695SRobert Joslyn 
sel3350_power_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)90*81ad5695SRobert Joslyn static int sel3350_power_get_property(struct power_supply *psy,
91*81ad5695SRobert Joslyn 				      enum power_supply_property psp,
92*81ad5695SRobert Joslyn 				      union power_supply_propval *val)
93*81ad5695SRobert Joslyn {
94*81ad5695SRobert Joslyn 	struct sel3350_power_cfg_data *data = power_supply_get_drvdata(psy);
95*81ad5695SRobert Joslyn 
96*81ad5695SRobert Joslyn 	switch (psp) {
97*81ad5695SRobert Joslyn 	case POWER_SUPPLY_PROP_HEALTH:
98*81ad5695SRobert Joslyn 		if (gpiod_get_value(data->ps_detect)) {
99*81ad5695SRobert Joslyn 			if (gpiod_get_value(data->ps_good))
100*81ad5695SRobert Joslyn 				val->intval = POWER_SUPPLY_HEALTH_GOOD;
101*81ad5695SRobert Joslyn 			else
102*81ad5695SRobert Joslyn 				val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
103*81ad5695SRobert Joslyn 		} else {
104*81ad5695SRobert Joslyn 			val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
105*81ad5695SRobert Joslyn 		}
106*81ad5695SRobert Joslyn 		break;
107*81ad5695SRobert Joslyn 	case POWER_SUPPLY_PROP_PRESENT:
108*81ad5695SRobert Joslyn 		val->intval = gpiod_get_value(data->ps_detect);
109*81ad5695SRobert Joslyn 		break;
110*81ad5695SRobert Joslyn 	case POWER_SUPPLY_PROP_ONLINE:
111*81ad5695SRobert Joslyn 		val->intval = gpiod_get_value(data->ps_good);
112*81ad5695SRobert Joslyn 		break;
113*81ad5695SRobert Joslyn 	default:
114*81ad5695SRobert Joslyn 		return -EINVAL;
115*81ad5695SRobert Joslyn 	}
116*81ad5695SRobert Joslyn 	return 0;
117*81ad5695SRobert Joslyn }
118*81ad5695SRobert Joslyn 
119*81ad5695SRobert Joslyn static const enum power_supply_property sel3350_power_properties[] = {
120*81ad5695SRobert Joslyn 	POWER_SUPPLY_PROP_HEALTH,
121*81ad5695SRobert Joslyn 	POWER_SUPPLY_PROP_PRESENT,
122*81ad5695SRobert Joslyn 	POWER_SUPPLY_PROP_ONLINE,
123*81ad5695SRobert Joslyn };
124*81ad5695SRobert Joslyn 
125*81ad5695SRobert Joslyn static const struct power_supply_desc sel3350_ps_a_desc = {
126*81ad5695SRobert Joslyn 	.name = SEL_PS_A,
127*81ad5695SRobert Joslyn 	.type = POWER_SUPPLY_TYPE_MAINS,
128*81ad5695SRobert Joslyn 	.properties = sel3350_power_properties,
129*81ad5695SRobert Joslyn 	.num_properties = ARRAY_SIZE(sel3350_power_properties),
130*81ad5695SRobert Joslyn 	.get_property = sel3350_power_get_property,
131*81ad5695SRobert Joslyn };
132*81ad5695SRobert Joslyn 
133*81ad5695SRobert Joslyn static const struct power_supply_desc sel3350_ps_b_desc = {
134*81ad5695SRobert Joslyn 	.name = SEL_PS_B,
135*81ad5695SRobert Joslyn 	.type = POWER_SUPPLY_TYPE_MAINS,
136*81ad5695SRobert Joslyn 	.properties = sel3350_power_properties,
137*81ad5695SRobert Joslyn 	.num_properties = ARRAY_SIZE(sel3350_power_properties),
138*81ad5695SRobert Joslyn 	.get_property = sel3350_power_get_property,
139*81ad5695SRobert Joslyn };
140*81ad5695SRobert Joslyn 
141*81ad5695SRobert Joslyn struct sel3350_data {
142*81ad5695SRobert Joslyn 	struct platform_device *leds_pdev;
143*81ad5695SRobert Joslyn 	struct power_supply *ps_a;
144*81ad5695SRobert Joslyn 	struct power_supply *ps_b;
145*81ad5695SRobert Joslyn 	struct sel3350_power_cfg_data ps_a_cfg_data;
146*81ad5695SRobert Joslyn 	struct sel3350_power_cfg_data ps_b_cfg_data;
147*81ad5695SRobert Joslyn };
148*81ad5695SRobert Joslyn 
sel3350_probe(struct platform_device * pdev)149*81ad5695SRobert Joslyn static int sel3350_probe(struct platform_device *pdev)
150*81ad5695SRobert Joslyn {
151*81ad5695SRobert Joslyn 	int rs;
152*81ad5695SRobert Joslyn 	struct sel3350_data *sel3350;
153*81ad5695SRobert Joslyn 	struct power_supply_config ps_cfg = {};
154*81ad5695SRobert Joslyn 
155*81ad5695SRobert Joslyn 	sel3350 = devm_kzalloc(&pdev->dev, sizeof(struct sel3350_data), GFP_KERNEL);
156*81ad5695SRobert Joslyn 	if (!sel3350)
157*81ad5695SRobert Joslyn 		return -ENOMEM;
158*81ad5695SRobert Joslyn 
159*81ad5695SRobert Joslyn 	platform_set_drvdata(pdev, sel3350);
160*81ad5695SRobert Joslyn 
161*81ad5695SRobert Joslyn 	gpiod_add_lookup_table(&sel3350_leds_table);
162*81ad5695SRobert Joslyn 	gpiod_add_lookup_table(&sel3350_gpios_table);
163*81ad5695SRobert Joslyn 
164*81ad5695SRobert Joslyn 	sel3350->leds_pdev = platform_device_register_data(
165*81ad5695SRobert Joslyn 			NULL,
166*81ad5695SRobert Joslyn 			"leds-gpio",
167*81ad5695SRobert Joslyn 			PLATFORM_DEVID_NONE,
168*81ad5695SRobert Joslyn 			&sel3350_leds_pdata,
169*81ad5695SRobert Joslyn 			sizeof(sel3350_leds_pdata));
170*81ad5695SRobert Joslyn 	if (IS_ERR(sel3350->leds_pdev)) {
171*81ad5695SRobert Joslyn 		rs = PTR_ERR(sel3350->leds_pdev);
172*81ad5695SRobert Joslyn 		dev_err(&pdev->dev, "Failed registering platform device: %d\n", rs);
173*81ad5695SRobert Joslyn 		goto err_platform;
174*81ad5695SRobert Joslyn 	}
175*81ad5695SRobert Joslyn 
176*81ad5695SRobert Joslyn 	/* Power Supply A */
177*81ad5695SRobert Joslyn 	sel3350->ps_a_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev,
178*81ad5695SRobert Joslyn 							  SEL_PS_A_DETECT,
179*81ad5695SRobert Joslyn 							  GPIOD_IN);
180*81ad5695SRobert Joslyn 	sel3350->ps_a_cfg_data.ps_good = devm_gpiod_get(&pdev->dev,
181*81ad5695SRobert Joslyn 							SEL_PS_A_GOOD,
182*81ad5695SRobert Joslyn 							GPIOD_IN);
183*81ad5695SRobert Joslyn 	ps_cfg.drv_data = &sel3350->ps_a_cfg_data;
184*81ad5695SRobert Joslyn 	sel3350->ps_a = devm_power_supply_register(&pdev->dev,
185*81ad5695SRobert Joslyn 						   &sel3350_ps_a_desc,
186*81ad5695SRobert Joslyn 						   &ps_cfg);
187*81ad5695SRobert Joslyn 	if (IS_ERR(sel3350->ps_a)) {
188*81ad5695SRobert Joslyn 		rs = PTR_ERR(sel3350->ps_a);
189*81ad5695SRobert Joslyn 		dev_err(&pdev->dev, "Failed registering power supply A: %d\n", rs);
190*81ad5695SRobert Joslyn 		goto err_ps;
191*81ad5695SRobert Joslyn 	}
192*81ad5695SRobert Joslyn 
193*81ad5695SRobert Joslyn 	/* Power Supply B */
194*81ad5695SRobert Joslyn 	sel3350->ps_b_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev,
195*81ad5695SRobert Joslyn 							  SEL_PS_B_DETECT,
196*81ad5695SRobert Joslyn 							  GPIOD_IN);
197*81ad5695SRobert Joslyn 	sel3350->ps_b_cfg_data.ps_good = devm_gpiod_get(&pdev->dev,
198*81ad5695SRobert Joslyn 							SEL_PS_B_GOOD,
199*81ad5695SRobert Joslyn 							GPIOD_IN);
200*81ad5695SRobert Joslyn 	ps_cfg.drv_data = &sel3350->ps_b_cfg_data;
201*81ad5695SRobert Joslyn 	sel3350->ps_b = devm_power_supply_register(&pdev->dev,
202*81ad5695SRobert Joslyn 						   &sel3350_ps_b_desc,
203*81ad5695SRobert Joslyn 						   &ps_cfg);
204*81ad5695SRobert Joslyn 	if (IS_ERR(sel3350->ps_b)) {
205*81ad5695SRobert Joslyn 		rs = PTR_ERR(sel3350->ps_b);
206*81ad5695SRobert Joslyn 		dev_err(&pdev->dev, "Failed registering power supply B: %d\n", rs);
207*81ad5695SRobert Joslyn 		goto err_ps;
208*81ad5695SRobert Joslyn 	}
209*81ad5695SRobert Joslyn 
210*81ad5695SRobert Joslyn 	return 0;
211*81ad5695SRobert Joslyn 
212*81ad5695SRobert Joslyn err_ps:
213*81ad5695SRobert Joslyn 	platform_device_unregister(sel3350->leds_pdev);
214*81ad5695SRobert Joslyn err_platform:
215*81ad5695SRobert Joslyn 	gpiod_remove_lookup_table(&sel3350_gpios_table);
216*81ad5695SRobert Joslyn 	gpiod_remove_lookup_table(&sel3350_leds_table);
217*81ad5695SRobert Joslyn 
218*81ad5695SRobert Joslyn 	return rs;
219*81ad5695SRobert Joslyn }
220*81ad5695SRobert Joslyn 
sel3350_remove(struct platform_device * pdev)221*81ad5695SRobert Joslyn static int sel3350_remove(struct platform_device *pdev)
222*81ad5695SRobert Joslyn {
223*81ad5695SRobert Joslyn 	struct sel3350_data *sel3350 = platform_get_drvdata(pdev);
224*81ad5695SRobert Joslyn 
225*81ad5695SRobert Joslyn 	platform_device_unregister(sel3350->leds_pdev);
226*81ad5695SRobert Joslyn 	gpiod_remove_lookup_table(&sel3350_gpios_table);
227*81ad5695SRobert Joslyn 	gpiod_remove_lookup_table(&sel3350_leds_table);
228*81ad5695SRobert Joslyn 
229*81ad5695SRobert Joslyn 	return 0;
230*81ad5695SRobert Joslyn }
231*81ad5695SRobert Joslyn 
232*81ad5695SRobert Joslyn static const struct acpi_device_id sel3350_device_ids[] = {
233*81ad5695SRobert Joslyn 	{ B2093_GPIO_ACPI_ID, 0 },
234*81ad5695SRobert Joslyn 	{ "", 0 },
235*81ad5695SRobert Joslyn };
236*81ad5695SRobert Joslyn MODULE_DEVICE_TABLE(acpi, sel3350_device_ids);
237*81ad5695SRobert Joslyn 
238*81ad5695SRobert Joslyn static struct platform_driver sel3350_platform_driver = {
239*81ad5695SRobert Joslyn 	.probe = sel3350_probe,
240*81ad5695SRobert Joslyn 	.remove = sel3350_remove,
241*81ad5695SRobert Joslyn 	.driver = {
242*81ad5695SRobert Joslyn 		.name = "sel3350-platform",
243*81ad5695SRobert Joslyn 		.acpi_match_table = sel3350_device_ids,
244*81ad5695SRobert Joslyn 	},
245*81ad5695SRobert Joslyn };
246*81ad5695SRobert Joslyn module_platform_driver(sel3350_platform_driver);
247*81ad5695SRobert Joslyn 
248*81ad5695SRobert Joslyn MODULE_AUTHOR("Schweitzer Engineering Laboratories");
249*81ad5695SRobert Joslyn MODULE_DESCRIPTION("SEL-3350 platform driver");
250*81ad5695SRobert Joslyn MODULE_LICENSE("Dual BSD/GPL");
251*81ad5695SRobert Joslyn MODULE_SOFTDEP("pre: pinctrl_broxton leds-gpio");
252