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(data->ps_detect)) { 99 if (gpiod_get_value(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(data->ps_detect); 109 break; 110 case POWER_SUPPLY_PROP_ONLINE: 111 val->intval = gpiod_get_value(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(&pdev->dev, sizeof(struct sel3350_data), GFP_KERNEL); 156 if (!sel3350) 157 return -ENOMEM; 158 159 platform_set_drvdata(pdev, sel3350); 160 161 gpiod_add_lookup_table(&sel3350_leds_table); 162 gpiod_add_lookup_table(&sel3350_gpios_table); 163 164 sel3350->leds_pdev = platform_device_register_data( 165 NULL, 166 "leds-gpio", 167 PLATFORM_DEVID_NONE, 168 &sel3350_leds_pdata, 169 sizeof(sel3350_leds_pdata)); 170 if (IS_ERR(sel3350->leds_pdev)) { 171 rs = PTR_ERR(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(&pdev->dev, 178 SEL_PS_A_DETECT, 179 GPIOD_IN); 180 sel3350->ps_a_cfg_data.ps_good = devm_gpiod_get(&pdev->dev, 181 SEL_PS_A_GOOD, 182 GPIOD_IN); 183 ps_cfg.drv_data = &sel3350->ps_a_cfg_data; 184 sel3350->ps_a = devm_power_supply_register(&pdev->dev, 185 &sel3350_ps_a_desc, 186 &ps_cfg); 187 if (IS_ERR(sel3350->ps_a)) { 188 rs = PTR_ERR(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(&pdev->dev, 195 SEL_PS_B_DETECT, 196 GPIOD_IN); 197 sel3350->ps_b_cfg_data.ps_good = devm_gpiod_get(&pdev->dev, 198 SEL_PS_B_GOOD, 199 GPIOD_IN); 200 ps_cfg.drv_data = &sel3350->ps_b_cfg_data; 201 sel3350->ps_b = devm_power_supply_register(&pdev->dev, 202 &sel3350_ps_b_desc, 203 &ps_cfg); 204 if (IS_ERR(sel3350->ps_b)) { 205 rs = PTR_ERR(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(&sel3350_gpios_table); 216 gpiod_remove_lookup_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(&sel3350_gpios_table); 227 gpiod_remove_lookup_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 = 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