162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2023 Schweitzer Engineering Laboratories, Inc. 462306a36Sopenharmony_ci * 2350 NE Hopkins Court, Pullman, WA 99163 USA 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Platform support for the b2093 mainboard used in SEL-3350 computers. 762306a36Sopenharmony_ci * Consumes GPIO from the SoC to provide standard LED and power supply 862306a36Sopenharmony_ci * devices. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/acpi.h> 1262306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1362306a36Sopenharmony_ci#include <linux/gpio/machine.h> 1462306a36Sopenharmony_ci#include <linux/leds.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/power_supply.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* Broxton communities */ 2062306a36Sopenharmony_ci#define BXT_NW "INT3452:01" 2162306a36Sopenharmony_ci#define BXT_W "INT3452:02" 2262306a36Sopenharmony_ci#define BXT_SW "INT3452:03" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define B2093_GPIO_ACPI_ID "SEL0003" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define SEL_PS_A "sel_ps_a" 2762306a36Sopenharmony_ci#define SEL_PS_A_DETECT "sel_ps_a_detect" 2862306a36Sopenharmony_ci#define SEL_PS_A_GOOD "sel_ps_a_good" 2962306a36Sopenharmony_ci#define SEL_PS_B "sel_ps_b" 3062306a36Sopenharmony_ci#define SEL_PS_B_DETECT "sel_ps_b_detect" 3162306a36Sopenharmony_ci#define SEL_PS_B_GOOD "sel_ps_b_good" 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* LEDs */ 3462306a36Sopenharmony_cistatic const struct gpio_led sel3350_leds[] = { 3562306a36Sopenharmony_ci { .name = "sel:green:aux1" }, 3662306a36Sopenharmony_ci { .name = "sel:green:aux2" }, 3762306a36Sopenharmony_ci { .name = "sel:green:aux3" }, 3862306a36Sopenharmony_ci { .name = "sel:green:aux4" }, 3962306a36Sopenharmony_ci { .name = "sel:red:alarm" }, 4062306a36Sopenharmony_ci { .name = "sel:green:enabled", 4162306a36Sopenharmony_ci .default_state = LEDS_GPIO_DEFSTATE_ON }, 4262306a36Sopenharmony_ci { .name = "sel:red:aux1" }, 4362306a36Sopenharmony_ci { .name = "sel:red:aux2" }, 4462306a36Sopenharmony_ci { .name = "sel:red:aux3" }, 4562306a36Sopenharmony_ci { .name = "sel:red:aux4" }, 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic const struct gpio_led_platform_data sel3350_leds_pdata = { 4962306a36Sopenharmony_ci .num_leds = ARRAY_SIZE(sel3350_leds), 5062306a36Sopenharmony_ci .leds = sel3350_leds, 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci/* Map GPIOs to LEDs */ 5462306a36Sopenharmony_cistatic struct gpiod_lookup_table sel3350_leds_table = { 5562306a36Sopenharmony_ci .dev_id = "leds-gpio", 5662306a36Sopenharmony_ci .table = { 5762306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_NW, 49, NULL, 0, GPIO_ACTIVE_HIGH), 5862306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_NW, 50, NULL, 1, GPIO_ACTIVE_HIGH), 5962306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_NW, 51, NULL, 2, GPIO_ACTIVE_HIGH), 6062306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_NW, 52, NULL, 3, GPIO_ACTIVE_HIGH), 6162306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_W, 20, NULL, 4, GPIO_ACTIVE_HIGH), 6262306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_W, 21, NULL, 5, GPIO_ACTIVE_HIGH), 6362306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_SW, 37, NULL, 6, GPIO_ACTIVE_HIGH), 6462306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_SW, 38, NULL, 7, GPIO_ACTIVE_HIGH), 6562306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_SW, 39, NULL, 8, GPIO_ACTIVE_HIGH), 6662306a36Sopenharmony_ci GPIO_LOOKUP_IDX(BXT_SW, 40, NULL, 9, GPIO_ACTIVE_HIGH), 6762306a36Sopenharmony_ci {}, 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci}; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/* Map GPIOs to power supplies */ 7262306a36Sopenharmony_cistatic struct gpiod_lookup_table sel3350_gpios_table = { 7362306a36Sopenharmony_ci .dev_id = B2093_GPIO_ACPI_ID ":00", 7462306a36Sopenharmony_ci .table = { 7562306a36Sopenharmony_ci GPIO_LOOKUP(BXT_NW, 44, SEL_PS_A_DETECT, GPIO_ACTIVE_LOW), 7662306a36Sopenharmony_ci GPIO_LOOKUP(BXT_NW, 45, SEL_PS_A_GOOD, GPIO_ACTIVE_LOW), 7762306a36Sopenharmony_ci GPIO_LOOKUP(BXT_NW, 46, SEL_PS_B_DETECT, GPIO_ACTIVE_LOW), 7862306a36Sopenharmony_ci GPIO_LOOKUP(BXT_NW, 47, SEL_PS_B_GOOD, GPIO_ACTIVE_LOW), 7962306a36Sopenharmony_ci {}, 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci}; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci/* Power Supplies */ 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistruct sel3350_power_cfg_data { 8662306a36Sopenharmony_ci struct gpio_desc *ps_detect; 8762306a36Sopenharmony_ci struct gpio_desc *ps_good; 8862306a36Sopenharmony_ci}; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic int sel3350_power_get_property(struct power_supply *psy, 9162306a36Sopenharmony_ci enum power_supply_property psp, 9262306a36Sopenharmony_ci union power_supply_propval *val) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct sel3350_power_cfg_data *data = power_supply_get_drvdata(psy); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci switch (psp) { 9762306a36Sopenharmony_ci case POWER_SUPPLY_PROP_HEALTH: 9862306a36Sopenharmony_ci if (gpiod_get_value(data->ps_detect)) { 9962306a36Sopenharmony_ci if (gpiod_get_value(data->ps_good)) 10062306a36Sopenharmony_ci val->intval = POWER_SUPPLY_HEALTH_GOOD; 10162306a36Sopenharmony_ci else 10262306a36Sopenharmony_ci val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 10362306a36Sopenharmony_ci } else { 10462306a36Sopenharmony_ci val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci break; 10762306a36Sopenharmony_ci case POWER_SUPPLY_PROP_PRESENT: 10862306a36Sopenharmony_ci val->intval = gpiod_get_value(data->ps_detect); 10962306a36Sopenharmony_ci break; 11062306a36Sopenharmony_ci case POWER_SUPPLY_PROP_ONLINE: 11162306a36Sopenharmony_ci val->intval = gpiod_get_value(data->ps_good); 11262306a36Sopenharmony_ci break; 11362306a36Sopenharmony_ci default: 11462306a36Sopenharmony_ci return -EINVAL; 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci return 0; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic const enum power_supply_property sel3350_power_properties[] = { 12062306a36Sopenharmony_ci POWER_SUPPLY_PROP_HEALTH, 12162306a36Sopenharmony_ci POWER_SUPPLY_PROP_PRESENT, 12262306a36Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic const struct power_supply_desc sel3350_ps_a_desc = { 12662306a36Sopenharmony_ci .name = SEL_PS_A, 12762306a36Sopenharmony_ci .type = POWER_SUPPLY_TYPE_MAINS, 12862306a36Sopenharmony_ci .properties = sel3350_power_properties, 12962306a36Sopenharmony_ci .num_properties = ARRAY_SIZE(sel3350_power_properties), 13062306a36Sopenharmony_ci .get_property = sel3350_power_get_property, 13162306a36Sopenharmony_ci}; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic const struct power_supply_desc sel3350_ps_b_desc = { 13462306a36Sopenharmony_ci .name = SEL_PS_B, 13562306a36Sopenharmony_ci .type = POWER_SUPPLY_TYPE_MAINS, 13662306a36Sopenharmony_ci .properties = sel3350_power_properties, 13762306a36Sopenharmony_ci .num_properties = ARRAY_SIZE(sel3350_power_properties), 13862306a36Sopenharmony_ci .get_property = sel3350_power_get_property, 13962306a36Sopenharmony_ci}; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistruct sel3350_data { 14262306a36Sopenharmony_ci struct platform_device *leds_pdev; 14362306a36Sopenharmony_ci struct power_supply *ps_a; 14462306a36Sopenharmony_ci struct power_supply *ps_b; 14562306a36Sopenharmony_ci struct sel3350_power_cfg_data ps_a_cfg_data; 14662306a36Sopenharmony_ci struct sel3350_power_cfg_data ps_b_cfg_data; 14762306a36Sopenharmony_ci}; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic int sel3350_probe(struct platform_device *pdev) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci int rs; 15262306a36Sopenharmony_ci struct sel3350_data *sel3350; 15362306a36Sopenharmony_ci struct power_supply_config ps_cfg = {}; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci sel3350 = devm_kzalloc(&pdev->dev, sizeof(struct sel3350_data), GFP_KERNEL); 15662306a36Sopenharmony_ci if (!sel3350) 15762306a36Sopenharmony_ci return -ENOMEM; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci platform_set_drvdata(pdev, sel3350); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci gpiod_add_lookup_table(&sel3350_leds_table); 16262306a36Sopenharmony_ci gpiod_add_lookup_table(&sel3350_gpios_table); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci sel3350->leds_pdev = platform_device_register_data( 16562306a36Sopenharmony_ci NULL, 16662306a36Sopenharmony_ci "leds-gpio", 16762306a36Sopenharmony_ci PLATFORM_DEVID_NONE, 16862306a36Sopenharmony_ci &sel3350_leds_pdata, 16962306a36Sopenharmony_ci sizeof(sel3350_leds_pdata)); 17062306a36Sopenharmony_ci if (IS_ERR(sel3350->leds_pdev)) { 17162306a36Sopenharmony_ci rs = PTR_ERR(sel3350->leds_pdev); 17262306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed registering platform device: %d\n", rs); 17362306a36Sopenharmony_ci goto err_platform; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci /* Power Supply A */ 17762306a36Sopenharmony_ci sel3350->ps_a_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev, 17862306a36Sopenharmony_ci SEL_PS_A_DETECT, 17962306a36Sopenharmony_ci GPIOD_IN); 18062306a36Sopenharmony_ci sel3350->ps_a_cfg_data.ps_good = devm_gpiod_get(&pdev->dev, 18162306a36Sopenharmony_ci SEL_PS_A_GOOD, 18262306a36Sopenharmony_ci GPIOD_IN); 18362306a36Sopenharmony_ci ps_cfg.drv_data = &sel3350->ps_a_cfg_data; 18462306a36Sopenharmony_ci sel3350->ps_a = devm_power_supply_register(&pdev->dev, 18562306a36Sopenharmony_ci &sel3350_ps_a_desc, 18662306a36Sopenharmony_ci &ps_cfg); 18762306a36Sopenharmony_ci if (IS_ERR(sel3350->ps_a)) { 18862306a36Sopenharmony_ci rs = PTR_ERR(sel3350->ps_a); 18962306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed registering power supply A: %d\n", rs); 19062306a36Sopenharmony_ci goto err_ps; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* Power Supply B */ 19462306a36Sopenharmony_ci sel3350->ps_b_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev, 19562306a36Sopenharmony_ci SEL_PS_B_DETECT, 19662306a36Sopenharmony_ci GPIOD_IN); 19762306a36Sopenharmony_ci sel3350->ps_b_cfg_data.ps_good = devm_gpiod_get(&pdev->dev, 19862306a36Sopenharmony_ci SEL_PS_B_GOOD, 19962306a36Sopenharmony_ci GPIOD_IN); 20062306a36Sopenharmony_ci ps_cfg.drv_data = &sel3350->ps_b_cfg_data; 20162306a36Sopenharmony_ci sel3350->ps_b = devm_power_supply_register(&pdev->dev, 20262306a36Sopenharmony_ci &sel3350_ps_b_desc, 20362306a36Sopenharmony_ci &ps_cfg); 20462306a36Sopenharmony_ci if (IS_ERR(sel3350->ps_b)) { 20562306a36Sopenharmony_ci rs = PTR_ERR(sel3350->ps_b); 20662306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed registering power supply B: %d\n", rs); 20762306a36Sopenharmony_ci goto err_ps; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci return 0; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cierr_ps: 21362306a36Sopenharmony_ci platform_device_unregister(sel3350->leds_pdev); 21462306a36Sopenharmony_cierr_platform: 21562306a36Sopenharmony_ci gpiod_remove_lookup_table(&sel3350_gpios_table); 21662306a36Sopenharmony_ci gpiod_remove_lookup_table(&sel3350_leds_table); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci return rs; 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic int sel3350_remove(struct platform_device *pdev) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci struct sel3350_data *sel3350 = platform_get_drvdata(pdev); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci platform_device_unregister(sel3350->leds_pdev); 22662306a36Sopenharmony_ci gpiod_remove_lookup_table(&sel3350_gpios_table); 22762306a36Sopenharmony_ci gpiod_remove_lookup_table(&sel3350_leds_table); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic const struct acpi_device_id sel3350_device_ids[] = { 23362306a36Sopenharmony_ci { B2093_GPIO_ACPI_ID, 0 }, 23462306a36Sopenharmony_ci { "", 0 }, 23562306a36Sopenharmony_ci}; 23662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, sel3350_device_ids); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic struct platform_driver sel3350_platform_driver = { 23962306a36Sopenharmony_ci .probe = sel3350_probe, 24062306a36Sopenharmony_ci .remove = sel3350_remove, 24162306a36Sopenharmony_ci .driver = { 24262306a36Sopenharmony_ci .name = "sel3350-platform", 24362306a36Sopenharmony_ci .acpi_match_table = sel3350_device_ids, 24462306a36Sopenharmony_ci }, 24562306a36Sopenharmony_ci}; 24662306a36Sopenharmony_cimodule_platform_driver(sel3350_platform_driver); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ciMODULE_AUTHOR("Schweitzer Engineering Laboratories"); 24962306a36Sopenharmony_ciMODULE_DESCRIPTION("SEL-3350 platform driver"); 25062306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 25162306a36Sopenharmony_ciMODULE_SOFTDEP("pre: pinctrl_broxton leds-gpio"); 252