15b6c26a9SCarlo Caione /* 25b6c26a9SCarlo Caione * axp20x power button driver. 35b6c26a9SCarlo Caione * 45b6c26a9SCarlo Caione * Copyright (C) 2013 Carlo Caione <carlo@caione.org> 55b6c26a9SCarlo Caione * 65b6c26a9SCarlo Caione * This file is subject to the terms and conditions of the GNU General 75b6c26a9SCarlo Caione * Public License. See the file "COPYING" in the main directory of this 85b6c26a9SCarlo Caione * archive for more details. 95b6c26a9SCarlo Caione * 105b6c26a9SCarlo Caione * This program is distributed in the hope that it will be useful, 115b6c26a9SCarlo Caione * but WITHOUT ANY WARRANTY; without even the implied warranty of 125b6c26a9SCarlo Caione * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 135b6c26a9SCarlo Caione * GNU General Public License for more details. 145b6c26a9SCarlo Caione */ 155b6c26a9SCarlo Caione 169b13a4caSHans de Goede #include <linux/acpi.h> 175b6c26a9SCarlo Caione #include <linux/errno.h> 185b6c26a9SCarlo Caione #include <linux/irq.h> 195b6c26a9SCarlo Caione #include <linux/init.h> 205b6c26a9SCarlo Caione #include <linux/input.h> 215b6c26a9SCarlo Caione #include <linux/interrupt.h> 225b6c26a9SCarlo Caione #include <linux/kernel.h> 235b6c26a9SCarlo Caione #include <linux/mfd/axp20x.h> 245b6c26a9SCarlo Caione #include <linux/module.h> 255b6c26a9SCarlo Caione #include <linux/platform_device.h> 265b6c26a9SCarlo Caione #include <linux/regmap.h> 275b6c26a9SCarlo Caione #include <linux/slab.h> 285b6c26a9SCarlo Caione 295b6c26a9SCarlo Caione #define AXP20X_PEK_STARTUP_MASK (0xc0) 305b6c26a9SCarlo Caione #define AXP20X_PEK_SHUTDOWN_MASK (0x03) 315b6c26a9SCarlo Caione 325b6c26a9SCarlo Caione struct axp20x_pek { 335b6c26a9SCarlo Caione struct axp20x_dev *axp20x; 345b6c26a9SCarlo Caione struct input_dev *input; 355b6c26a9SCarlo Caione int irq_dbr; 365b6c26a9SCarlo Caione int irq_dbf; 375b6c26a9SCarlo Caione }; 385b6c26a9SCarlo Caione 395b6c26a9SCarlo Caione struct axp20x_time { 405b6c26a9SCarlo Caione unsigned int time; 415b6c26a9SCarlo Caione unsigned int idx; 425b6c26a9SCarlo Caione }; 435b6c26a9SCarlo Caione 445b6c26a9SCarlo Caione static const struct axp20x_time startup_time[] = { 455b6c26a9SCarlo Caione { .time = 128, .idx = 0 }, 465b6c26a9SCarlo Caione { .time = 1000, .idx = 2 }, 475b6c26a9SCarlo Caione { .time = 3000, .idx = 1 }, 485b6c26a9SCarlo Caione { .time = 2000, .idx = 3 }, 495b6c26a9SCarlo Caione }; 505b6c26a9SCarlo Caione 515b6c26a9SCarlo Caione static const struct axp20x_time shutdown_time[] = { 525b6c26a9SCarlo Caione { .time = 4000, .idx = 0 }, 535b6c26a9SCarlo Caione { .time = 6000, .idx = 1 }, 545b6c26a9SCarlo Caione { .time = 8000, .idx = 2 }, 555b6c26a9SCarlo Caione { .time = 10000, .idx = 3 }, 565b6c26a9SCarlo Caione }; 575b6c26a9SCarlo Caione 585b6c26a9SCarlo Caione struct axp20x_pek_ext_attr { 595b6c26a9SCarlo Caione const struct axp20x_time *p_time; 605b6c26a9SCarlo Caione unsigned int mask; 615b6c26a9SCarlo Caione }; 625b6c26a9SCarlo Caione 635b6c26a9SCarlo Caione static struct axp20x_pek_ext_attr axp20x_pek_startup_ext_attr = { 645b6c26a9SCarlo Caione .p_time = startup_time, 655b6c26a9SCarlo Caione .mask = AXP20X_PEK_STARTUP_MASK, 665b6c26a9SCarlo Caione }; 675b6c26a9SCarlo Caione 685b6c26a9SCarlo Caione static struct axp20x_pek_ext_attr axp20x_pek_shutdown_ext_attr = { 695b6c26a9SCarlo Caione .p_time = shutdown_time, 705b6c26a9SCarlo Caione .mask = AXP20X_PEK_SHUTDOWN_MASK, 715b6c26a9SCarlo Caione }; 725b6c26a9SCarlo Caione 735b6c26a9SCarlo Caione static struct axp20x_pek_ext_attr *get_axp_ext_attr(struct device_attribute *attr) 745b6c26a9SCarlo Caione { 755b6c26a9SCarlo Caione return container_of(attr, struct dev_ext_attribute, attr)->var; 765b6c26a9SCarlo Caione } 775b6c26a9SCarlo Caione 785b6c26a9SCarlo Caione static ssize_t axp20x_show_ext_attr(struct device *dev, 795b6c26a9SCarlo Caione struct device_attribute *attr, char *buf) 805b6c26a9SCarlo Caione { 815b6c26a9SCarlo Caione struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); 825b6c26a9SCarlo Caione struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr); 835b6c26a9SCarlo Caione unsigned int val; 845b6c26a9SCarlo Caione int ret, i; 855b6c26a9SCarlo Caione 865b6c26a9SCarlo Caione ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val); 875b6c26a9SCarlo Caione if (ret != 0) 885b6c26a9SCarlo Caione return ret; 895b6c26a9SCarlo Caione 905b6c26a9SCarlo Caione val &= axp20x_ea->mask; 915b6c26a9SCarlo Caione val >>= ffs(axp20x_ea->mask) - 1; 925b6c26a9SCarlo Caione 935b6c26a9SCarlo Caione for (i = 0; i < 4; i++) 945b6c26a9SCarlo Caione if (val == axp20x_ea->p_time[i].idx) 955b6c26a9SCarlo Caione val = axp20x_ea->p_time[i].time; 965b6c26a9SCarlo Caione 975b6c26a9SCarlo Caione return sprintf(buf, "%u\n", val); 985b6c26a9SCarlo Caione } 995b6c26a9SCarlo Caione 1005b6c26a9SCarlo Caione static ssize_t axp20x_store_ext_attr(struct device *dev, 1015b6c26a9SCarlo Caione struct device_attribute *attr, 1025b6c26a9SCarlo Caione const char *buf, size_t count) 1035b6c26a9SCarlo Caione { 1045b6c26a9SCarlo Caione struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); 1055b6c26a9SCarlo Caione struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr); 1065b6c26a9SCarlo Caione char val_str[20]; 1075b6c26a9SCarlo Caione size_t len; 1085b6c26a9SCarlo Caione int ret, i; 1095b6c26a9SCarlo Caione unsigned int val, idx = 0; 1105b6c26a9SCarlo Caione unsigned int best_err = UINT_MAX; 1115b6c26a9SCarlo Caione 1125b6c26a9SCarlo Caione val_str[sizeof(val_str) - 1] = '\0'; 1135b6c26a9SCarlo Caione strncpy(val_str, buf, sizeof(val_str) - 1); 1145b6c26a9SCarlo Caione len = strlen(val_str); 1155b6c26a9SCarlo Caione 1165b6c26a9SCarlo Caione if (len && val_str[len - 1] == '\n') 1175b6c26a9SCarlo Caione val_str[len - 1] = '\0'; 1185b6c26a9SCarlo Caione 1195b6c26a9SCarlo Caione ret = kstrtouint(val_str, 10, &val); 1205b6c26a9SCarlo Caione if (ret) 1215b6c26a9SCarlo Caione return ret; 1225b6c26a9SCarlo Caione 1235b6c26a9SCarlo Caione for (i = 3; i >= 0; i--) { 1245b6c26a9SCarlo Caione unsigned int err; 1255b6c26a9SCarlo Caione 1265b6c26a9SCarlo Caione err = abs(axp20x_ea->p_time[i].time - val); 1275b6c26a9SCarlo Caione if (err < best_err) { 1285b6c26a9SCarlo Caione best_err = err; 1295b6c26a9SCarlo Caione idx = axp20x_ea->p_time[i].idx; 1305b6c26a9SCarlo Caione } 1315b6c26a9SCarlo Caione 1325b6c26a9SCarlo Caione if (!err) 1335b6c26a9SCarlo Caione break; 1345b6c26a9SCarlo Caione } 1355b6c26a9SCarlo Caione 1365b6c26a9SCarlo Caione idx <<= ffs(axp20x_ea->mask) - 1; 1375b6c26a9SCarlo Caione ret = regmap_update_bits(axp20x_pek->axp20x->regmap, 1385b6c26a9SCarlo Caione AXP20X_PEK_KEY, 1395b6c26a9SCarlo Caione axp20x_ea->mask, idx); 1405b6c26a9SCarlo Caione if (ret != 0) 1415b6c26a9SCarlo Caione return -EINVAL; 142b388de88SDmitry Torokhov 1435b6c26a9SCarlo Caione return count; 1445b6c26a9SCarlo Caione } 1455b6c26a9SCarlo Caione 1465b6c26a9SCarlo Caione static struct dev_ext_attribute axp20x_dev_attr_startup = { 1475b6c26a9SCarlo Caione .attr = __ATTR(startup, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr), 148b388de88SDmitry Torokhov .var = &axp20x_pek_startup_ext_attr, 1495b6c26a9SCarlo Caione }; 1505b6c26a9SCarlo Caione 1515b6c26a9SCarlo Caione static struct dev_ext_attribute axp20x_dev_attr_shutdown = { 1525b6c26a9SCarlo Caione .attr = __ATTR(shutdown, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr), 153b388de88SDmitry Torokhov .var = &axp20x_pek_shutdown_ext_attr, 154b388de88SDmitry Torokhov }; 155b388de88SDmitry Torokhov 156b388de88SDmitry Torokhov static struct attribute *axp20x_attributes[] = { 157b388de88SDmitry Torokhov &axp20x_dev_attr_startup.attr.attr, 158b388de88SDmitry Torokhov &axp20x_dev_attr_shutdown.attr.attr, 159b388de88SDmitry Torokhov NULL, 160b388de88SDmitry Torokhov }; 161b388de88SDmitry Torokhov 162b388de88SDmitry Torokhov static const struct attribute_group axp20x_attribute_group = { 163b388de88SDmitry Torokhov .attrs = axp20x_attributes, 1645b6c26a9SCarlo Caione }; 1655b6c26a9SCarlo Caione 1665b6c26a9SCarlo Caione static irqreturn_t axp20x_pek_irq(int irq, void *pwr) 1675b6c26a9SCarlo Caione { 1685b6c26a9SCarlo Caione struct input_dev *idev = pwr; 1695b6c26a9SCarlo Caione struct axp20x_pek *axp20x_pek = input_get_drvdata(idev); 1705b6c26a9SCarlo Caione 171eeeee40fSHans de Goede /* 172eeeee40fSHans de Goede * The power-button is connected to ground so a falling edge (dbf) 173eeeee40fSHans de Goede * means it is pressed. 174eeeee40fSHans de Goede */ 175eeeee40fSHans de Goede if (irq == axp20x_pek->irq_dbf) 1765b6c26a9SCarlo Caione input_report_key(idev, KEY_POWER, true); 177eeeee40fSHans de Goede else if (irq == axp20x_pek->irq_dbr) 1785b6c26a9SCarlo Caione input_report_key(idev, KEY_POWER, false); 1795b6c26a9SCarlo Caione 1805b6c26a9SCarlo Caione input_sync(idev); 1815b6c26a9SCarlo Caione 1825b6c26a9SCarlo Caione return IRQ_HANDLED; 1835b6c26a9SCarlo Caione } 1845b6c26a9SCarlo Caione 185b388de88SDmitry Torokhov static void axp20x_remove_sysfs_group(void *_data) 186b388de88SDmitry Torokhov { 187b388de88SDmitry Torokhov struct device *dev = _data; 188b388de88SDmitry Torokhov 189b388de88SDmitry Torokhov sysfs_remove_group(&dev->kobj, &axp20x_attribute_group); 190b388de88SDmitry Torokhov } 191b388de88SDmitry Torokhov 192f2bd5a9eSHans de Goede static int axp20x_pek_probe_input_device(struct axp20x_pek *axp20x_pek, 193f2bd5a9eSHans de Goede struct platform_device *pdev) 1945b6c26a9SCarlo Caione { 195f2bd5a9eSHans de Goede struct axp20x_dev *axp20x = axp20x_pek->axp20x; 1965b6c26a9SCarlo Caione struct input_dev *idev; 1975b6c26a9SCarlo Caione int error; 1985b6c26a9SCarlo Caione 1995b6c26a9SCarlo Caione axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR"); 2005b6c26a9SCarlo Caione if (axp20x_pek->irq_dbr < 0) { 2015b6c26a9SCarlo Caione dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n", 2025b6c26a9SCarlo Caione axp20x_pek->irq_dbr); 2035b6c26a9SCarlo Caione return axp20x_pek->irq_dbr; 2045b6c26a9SCarlo Caione } 2055b6c26a9SCarlo Caione axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc, 2065b6c26a9SCarlo Caione axp20x_pek->irq_dbr); 2075b6c26a9SCarlo Caione 2085b6c26a9SCarlo Caione axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF"); 2095b6c26a9SCarlo Caione if (axp20x_pek->irq_dbf < 0) { 2105b6c26a9SCarlo Caione dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n", 2115b6c26a9SCarlo Caione axp20x_pek->irq_dbf); 2125b6c26a9SCarlo Caione return axp20x_pek->irq_dbf; 2135b6c26a9SCarlo Caione } 2145b6c26a9SCarlo Caione axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc, 2155b6c26a9SCarlo Caione axp20x_pek->irq_dbf); 2165b6c26a9SCarlo Caione 2175b6c26a9SCarlo Caione axp20x_pek->input = devm_input_allocate_device(&pdev->dev); 2185b6c26a9SCarlo Caione if (!axp20x_pek->input) 2195b6c26a9SCarlo Caione return -ENOMEM; 2205b6c26a9SCarlo Caione 2215b6c26a9SCarlo Caione idev = axp20x_pek->input; 2225b6c26a9SCarlo Caione 2235b6c26a9SCarlo Caione idev->name = "axp20x-pek"; 2245b6c26a9SCarlo Caione idev->phys = "m1kbd/input2"; 2255b6c26a9SCarlo Caione idev->dev.parent = &pdev->dev; 2265b6c26a9SCarlo Caione 2275b6c26a9SCarlo Caione input_set_capability(idev, EV_KEY, KEY_POWER); 2285b6c26a9SCarlo Caione 2295b6c26a9SCarlo Caione input_set_drvdata(idev, axp20x_pek); 2305b6c26a9SCarlo Caione 2315b6c26a9SCarlo Caione error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr, 2325b6c26a9SCarlo Caione axp20x_pek_irq, 0, 2335b6c26a9SCarlo Caione "axp20x-pek-dbr", idev); 2345b6c26a9SCarlo Caione if (error < 0) { 23573915f36SHans de Goede dev_err(&pdev->dev, "Failed to request dbr IRQ#%d: %d\n", 2365b6c26a9SCarlo Caione axp20x_pek->irq_dbr, error); 2375b6c26a9SCarlo Caione return error; 2385b6c26a9SCarlo Caione } 2395b6c26a9SCarlo Caione 2405b6c26a9SCarlo Caione error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf, 2415b6c26a9SCarlo Caione axp20x_pek_irq, 0, 2425b6c26a9SCarlo Caione "axp20x-pek-dbf", idev); 2435b6c26a9SCarlo Caione if (error < 0) { 24473915f36SHans de Goede dev_err(&pdev->dev, "Failed to request dbf IRQ#%d: %d\n", 2455b6c26a9SCarlo Caione axp20x_pek->irq_dbf, error); 2465b6c26a9SCarlo Caione return error; 2475b6c26a9SCarlo Caione } 2485b6c26a9SCarlo Caione 249f2bd5a9eSHans de Goede error = input_register_device(idev); 250f2bd5a9eSHans de Goede if (error) { 251f2bd5a9eSHans de Goede dev_err(&pdev->dev, "Can't register input device: %d\n", 252f2bd5a9eSHans de Goede error); 253f2bd5a9eSHans de Goede return error; 254f2bd5a9eSHans de Goede } 255f2bd5a9eSHans de Goede 256f2bd5a9eSHans de Goede return 0; 257f2bd5a9eSHans de Goede } 258f2bd5a9eSHans de Goede 2598d4b3137SHans de Goede #ifdef CONFIG_ACPI 2608d4b3137SHans de Goede static bool axp20x_pek_should_register_input(struct axp20x_pek *axp20x_pek, 2618d4b3137SHans de Goede struct platform_device *pdev) 2628d4b3137SHans de Goede { 2638d4b3137SHans de Goede unsigned long long hrv = 0; 2648d4b3137SHans de Goede acpi_status status; 2658d4b3137SHans de Goede 2668d4b3137SHans de Goede if (IS_ENABLED(CONFIG_INPUT_SOC_BUTTON_ARRAY) && 2678d4b3137SHans de Goede axp20x_pek->axp20x->variant == AXP288_ID) { 2688d4b3137SHans de Goede status = acpi_evaluate_integer(ACPI_HANDLE(pdev->dev.parent), 2698d4b3137SHans de Goede "_HRV", NULL, &hrv); 2708d4b3137SHans de Goede if (ACPI_FAILURE(status)) 2718d4b3137SHans de Goede dev_err(&pdev->dev, "Failed to get PMIC hardware revision\n"); 2728d4b3137SHans de Goede 2738d4b3137SHans de Goede /* 2748d4b3137SHans de Goede * On Cherry Trail platforms (hrv == 3), do not register the 2758d4b3137SHans de Goede * input device if there is an "INTCFD9" gpio 2768d4b3137SHans de Goede * button ACPI device, as that handles the power button too, 2778d4b3137SHans de Goede * and otherwise we end up reporting all presses twice. 2788d4b3137SHans de Goede */ 2798d4b3137SHans de Goede if (hrv == 3 && acpi_dev_found("INTCFD9")) 2808d4b3137SHans de Goede return false; 2818d4b3137SHans de Goede 2828d4b3137SHans de Goede } 2838d4b3137SHans de Goede 2848d4b3137SHans de Goede return true; 2858d4b3137SHans de Goede } 2868d4b3137SHans de Goede #else 2878d4b3137SHans de Goede static bool axp20x_pek_should_register_input(struct axp20x_pek *axp20x_pek, 2888d4b3137SHans de Goede struct platform_device *pdev) 2898d4b3137SHans de Goede { 2908d4b3137SHans de Goede return true; 2918d4b3137SHans de Goede } 2928d4b3137SHans de Goede #endif 2938d4b3137SHans de Goede 294f2bd5a9eSHans de Goede static int axp20x_pek_probe(struct platform_device *pdev) 295f2bd5a9eSHans de Goede { 296f2bd5a9eSHans de Goede struct axp20x_pek *axp20x_pek; 297f2bd5a9eSHans de Goede int error; 298f2bd5a9eSHans de Goede 299f2bd5a9eSHans de Goede axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek), 300f2bd5a9eSHans de Goede GFP_KERNEL); 301f2bd5a9eSHans de Goede if (!axp20x_pek) 302f2bd5a9eSHans de Goede return -ENOMEM; 303f2bd5a9eSHans de Goede 304f2bd5a9eSHans de Goede axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent); 305f2bd5a9eSHans de Goede 3068d4b3137SHans de Goede if (axp20x_pek_should_register_input(axp20x_pek, pdev)) { 307f2bd5a9eSHans de Goede error = axp20x_pek_probe_input_device(axp20x_pek, pdev); 308f2bd5a9eSHans de Goede if (error) 309f2bd5a9eSHans de Goede return error; 3109b13a4caSHans de Goede } 311f2bd5a9eSHans de Goede 312b388de88SDmitry Torokhov error = sysfs_create_group(&pdev->dev.kobj, &axp20x_attribute_group); 313b388de88SDmitry Torokhov if (error) { 31473915f36SHans de Goede dev_err(&pdev->dev, "Failed to create sysfs attributes: %d\n", 315b388de88SDmitry Torokhov error); 3165b6c26a9SCarlo Caione return error; 317b388de88SDmitry Torokhov } 3185b6c26a9SCarlo Caione 319b388de88SDmitry Torokhov error = devm_add_action(&pdev->dev, 320b388de88SDmitry Torokhov axp20x_remove_sysfs_group, &pdev->dev); 321b388de88SDmitry Torokhov if (error) { 322b388de88SDmitry Torokhov axp20x_remove_sysfs_group(&pdev->dev); 323b388de88SDmitry Torokhov dev_err(&pdev->dev, "Failed to add sysfs cleanup action: %d\n", 324b388de88SDmitry Torokhov error); 325b388de88SDmitry Torokhov return error; 326b388de88SDmitry Torokhov } 3275b6c26a9SCarlo Caione 328b388de88SDmitry Torokhov platform_set_drvdata(pdev, axp20x_pek); 3295b6c26a9SCarlo Caione 3305b6c26a9SCarlo Caione return 0; 3315b6c26a9SCarlo Caione } 3325b6c26a9SCarlo Caione 3335b6c26a9SCarlo Caione static struct platform_driver axp20x_pek_driver = { 3345b6c26a9SCarlo Caione .probe = axp20x_pek_probe, 3355b6c26a9SCarlo Caione .driver = { 3365b6c26a9SCarlo Caione .name = "axp20x-pek", 3375b6c26a9SCarlo Caione }, 3385b6c26a9SCarlo Caione }; 3395b6c26a9SCarlo Caione module_platform_driver(axp20x_pek_driver); 3405b6c26a9SCarlo Caione 3415b6c26a9SCarlo Caione MODULE_DESCRIPTION("axp20x Power Button"); 3425b6c26a9SCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 3435b6c26a9SCarlo Caione MODULE_LICENSE("GPL"); 344b6e26546SChen-Yu Tsai MODULE_ALIAS("platform:axp20x-pek"); 345