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