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 165b6c26a9SCarlo Caione #include <linux/errno.h> 175b6c26a9SCarlo Caione #include <linux/irq.h> 185b6c26a9SCarlo Caione #include <linux/init.h> 195b6c26a9SCarlo Caione #include <linux/input.h> 205b6c26a9SCarlo Caione #include <linux/interrupt.h> 215b6c26a9SCarlo Caione #include <linux/kernel.h> 225b6c26a9SCarlo Caione #include <linux/mfd/axp20x.h> 235b6c26a9SCarlo Caione #include <linux/module.h> 245b6c26a9SCarlo Caione #include <linux/platform_device.h> 255b6c26a9SCarlo Caione #include <linux/regmap.h> 265b6c26a9SCarlo Caione #include <linux/slab.h> 275b6c26a9SCarlo Caione 285b6c26a9SCarlo Caione #define AXP20X_PEK_STARTUP_MASK (0xc0) 295b6c26a9SCarlo Caione #define AXP20X_PEK_SHUTDOWN_MASK (0x03) 305b6c26a9SCarlo Caione 315b6c26a9SCarlo Caione struct axp20x_pek { 325b6c26a9SCarlo Caione struct axp20x_dev *axp20x; 335b6c26a9SCarlo Caione struct input_dev *input; 345b6c26a9SCarlo Caione int irq_dbr; 355b6c26a9SCarlo Caione int irq_dbf; 365b6c26a9SCarlo Caione }; 375b6c26a9SCarlo Caione 385b6c26a9SCarlo Caione struct axp20x_time { 395b6c26a9SCarlo Caione unsigned int time; 405b6c26a9SCarlo Caione unsigned int idx; 415b6c26a9SCarlo Caione }; 425b6c26a9SCarlo Caione 435b6c26a9SCarlo Caione static const struct axp20x_time startup_time[] = { 445b6c26a9SCarlo Caione { .time = 128, .idx = 0 }, 455b6c26a9SCarlo Caione { .time = 1000, .idx = 2 }, 465b6c26a9SCarlo Caione { .time = 3000, .idx = 1 }, 475b6c26a9SCarlo Caione { .time = 2000, .idx = 3 }, 485b6c26a9SCarlo Caione }; 495b6c26a9SCarlo Caione 505b6c26a9SCarlo Caione static const struct axp20x_time shutdown_time[] = { 515b6c26a9SCarlo Caione { .time = 4000, .idx = 0 }, 525b6c26a9SCarlo Caione { .time = 6000, .idx = 1 }, 535b6c26a9SCarlo Caione { .time = 8000, .idx = 2 }, 545b6c26a9SCarlo Caione { .time = 10000, .idx = 3 }, 555b6c26a9SCarlo Caione }; 565b6c26a9SCarlo Caione 575b6c26a9SCarlo Caione struct axp20x_pek_ext_attr { 585b6c26a9SCarlo Caione const struct axp20x_time *p_time; 595b6c26a9SCarlo Caione unsigned int mask; 605b6c26a9SCarlo Caione }; 615b6c26a9SCarlo Caione 625b6c26a9SCarlo Caione static struct axp20x_pek_ext_attr axp20x_pek_startup_ext_attr = { 635b6c26a9SCarlo Caione .p_time = startup_time, 645b6c26a9SCarlo Caione .mask = AXP20X_PEK_STARTUP_MASK, 655b6c26a9SCarlo Caione }; 665b6c26a9SCarlo Caione 675b6c26a9SCarlo Caione static struct axp20x_pek_ext_attr axp20x_pek_shutdown_ext_attr = { 685b6c26a9SCarlo Caione .p_time = shutdown_time, 695b6c26a9SCarlo Caione .mask = AXP20X_PEK_SHUTDOWN_MASK, 705b6c26a9SCarlo Caione }; 715b6c26a9SCarlo Caione 725b6c26a9SCarlo Caione static struct axp20x_pek_ext_attr *get_axp_ext_attr(struct device_attribute *attr) 735b6c26a9SCarlo Caione { 745b6c26a9SCarlo Caione return container_of(attr, struct dev_ext_attribute, attr)->var; 755b6c26a9SCarlo Caione } 765b6c26a9SCarlo Caione 775b6c26a9SCarlo Caione static ssize_t axp20x_show_ext_attr(struct device *dev, 785b6c26a9SCarlo Caione struct device_attribute *attr, char *buf) 795b6c26a9SCarlo Caione { 805b6c26a9SCarlo Caione struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); 815b6c26a9SCarlo Caione struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr); 825b6c26a9SCarlo Caione unsigned int val; 835b6c26a9SCarlo Caione int ret, i; 845b6c26a9SCarlo Caione 855b6c26a9SCarlo Caione ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val); 865b6c26a9SCarlo Caione if (ret != 0) 875b6c26a9SCarlo Caione return ret; 885b6c26a9SCarlo Caione 895b6c26a9SCarlo Caione val &= axp20x_ea->mask; 905b6c26a9SCarlo Caione val >>= ffs(axp20x_ea->mask) - 1; 915b6c26a9SCarlo Caione 925b6c26a9SCarlo Caione for (i = 0; i < 4; i++) 935b6c26a9SCarlo Caione if (val == axp20x_ea->p_time[i].idx) 945b6c26a9SCarlo Caione val = axp20x_ea->p_time[i].time; 955b6c26a9SCarlo Caione 965b6c26a9SCarlo Caione return sprintf(buf, "%u\n", val); 975b6c26a9SCarlo Caione } 985b6c26a9SCarlo Caione 995b6c26a9SCarlo Caione static ssize_t axp20x_store_ext_attr(struct device *dev, 1005b6c26a9SCarlo Caione struct device_attribute *attr, 1015b6c26a9SCarlo Caione const char *buf, size_t count) 1025b6c26a9SCarlo Caione { 1035b6c26a9SCarlo Caione struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); 1045b6c26a9SCarlo Caione struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr); 1055b6c26a9SCarlo Caione char val_str[20]; 1065b6c26a9SCarlo Caione size_t len; 1075b6c26a9SCarlo Caione int ret, i; 1085b6c26a9SCarlo Caione unsigned int val, idx = 0; 1095b6c26a9SCarlo Caione unsigned int best_err = UINT_MAX; 1105b6c26a9SCarlo Caione 1115b6c26a9SCarlo Caione val_str[sizeof(val_str) - 1] = '\0'; 1125b6c26a9SCarlo Caione strncpy(val_str, buf, sizeof(val_str) - 1); 1135b6c26a9SCarlo Caione len = strlen(val_str); 1145b6c26a9SCarlo Caione 1155b6c26a9SCarlo Caione if (len && val_str[len - 1] == '\n') 1165b6c26a9SCarlo Caione val_str[len - 1] = '\0'; 1175b6c26a9SCarlo Caione 1185b6c26a9SCarlo Caione ret = kstrtouint(val_str, 10, &val); 1195b6c26a9SCarlo Caione if (ret) 1205b6c26a9SCarlo Caione return ret; 1215b6c26a9SCarlo Caione 1225b6c26a9SCarlo Caione for (i = 3; i >= 0; i--) { 1235b6c26a9SCarlo Caione unsigned int err; 1245b6c26a9SCarlo Caione 1255b6c26a9SCarlo Caione err = abs(axp20x_ea->p_time[i].time - val); 1265b6c26a9SCarlo Caione if (err < best_err) { 1275b6c26a9SCarlo Caione best_err = err; 1285b6c26a9SCarlo Caione idx = axp20x_ea->p_time[i].idx; 1295b6c26a9SCarlo Caione } 1305b6c26a9SCarlo Caione 1315b6c26a9SCarlo Caione if (!err) 1325b6c26a9SCarlo Caione break; 1335b6c26a9SCarlo Caione } 1345b6c26a9SCarlo Caione 1355b6c26a9SCarlo Caione idx <<= ffs(axp20x_ea->mask) - 1; 1365b6c26a9SCarlo Caione ret = regmap_update_bits(axp20x_pek->axp20x->regmap, 1375b6c26a9SCarlo Caione AXP20X_PEK_KEY, 1385b6c26a9SCarlo Caione axp20x_ea->mask, idx); 1395b6c26a9SCarlo Caione if (ret != 0) 1405b6c26a9SCarlo Caione return -EINVAL; 141b388de88SDmitry Torokhov 1425b6c26a9SCarlo Caione return count; 1435b6c26a9SCarlo Caione } 1445b6c26a9SCarlo Caione 1455b6c26a9SCarlo Caione static struct dev_ext_attribute axp20x_dev_attr_startup = { 1465b6c26a9SCarlo Caione .attr = __ATTR(startup, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr), 147b388de88SDmitry Torokhov .var = &axp20x_pek_startup_ext_attr, 1485b6c26a9SCarlo Caione }; 1495b6c26a9SCarlo Caione 1505b6c26a9SCarlo Caione static struct dev_ext_attribute axp20x_dev_attr_shutdown = { 1515b6c26a9SCarlo Caione .attr = __ATTR(shutdown, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr), 152b388de88SDmitry Torokhov .var = &axp20x_pek_shutdown_ext_attr, 153b388de88SDmitry Torokhov }; 154b388de88SDmitry Torokhov 155b388de88SDmitry Torokhov static struct attribute *axp20x_attributes[] = { 156b388de88SDmitry Torokhov &axp20x_dev_attr_startup.attr.attr, 157b388de88SDmitry Torokhov &axp20x_dev_attr_shutdown.attr.attr, 158b388de88SDmitry Torokhov NULL, 159b388de88SDmitry Torokhov }; 160b388de88SDmitry Torokhov 161b388de88SDmitry Torokhov static const struct attribute_group axp20x_attribute_group = { 162b388de88SDmitry Torokhov .attrs = axp20x_attributes, 1635b6c26a9SCarlo Caione }; 1645b6c26a9SCarlo Caione 1655b6c26a9SCarlo Caione static irqreturn_t axp20x_pek_irq(int irq, void *pwr) 1665b6c26a9SCarlo Caione { 1675b6c26a9SCarlo Caione struct input_dev *idev = pwr; 1685b6c26a9SCarlo Caione struct axp20x_pek *axp20x_pek = input_get_drvdata(idev); 1695b6c26a9SCarlo Caione 170eeeee40fSHans de Goede /* 171eeeee40fSHans de Goede * The power-button is connected to ground so a falling edge (dbf) 172eeeee40fSHans de Goede * means it is pressed. 173eeeee40fSHans de Goede */ 174eeeee40fSHans de Goede if (irq == axp20x_pek->irq_dbf) 1755b6c26a9SCarlo Caione input_report_key(idev, KEY_POWER, true); 176eeeee40fSHans de Goede else if (irq == axp20x_pek->irq_dbr) 1775b6c26a9SCarlo Caione input_report_key(idev, KEY_POWER, false); 1785b6c26a9SCarlo Caione 1795b6c26a9SCarlo Caione input_sync(idev); 1805b6c26a9SCarlo Caione 1815b6c26a9SCarlo Caione return IRQ_HANDLED; 1825b6c26a9SCarlo Caione } 1835b6c26a9SCarlo Caione 184b388de88SDmitry Torokhov static void axp20x_remove_sysfs_group(void *_data) 185b388de88SDmitry Torokhov { 186b388de88SDmitry Torokhov struct device *dev = _data; 187b388de88SDmitry Torokhov 188b388de88SDmitry Torokhov sysfs_remove_group(&dev->kobj, &axp20x_attribute_group); 189b388de88SDmitry Torokhov } 190b388de88SDmitry Torokhov 1915b6c26a9SCarlo Caione static int axp20x_pek_probe(struct platform_device *pdev) 1925b6c26a9SCarlo Caione { 1935b6c26a9SCarlo Caione struct axp20x_pek *axp20x_pek; 1945b6c26a9SCarlo Caione struct axp20x_dev *axp20x; 1955b6c26a9SCarlo Caione struct input_dev *idev; 1965b6c26a9SCarlo Caione int error; 1975b6c26a9SCarlo Caione 1985b6c26a9SCarlo Caione axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek), 1995b6c26a9SCarlo Caione GFP_KERNEL); 2005b6c26a9SCarlo Caione if (!axp20x_pek) 2015b6c26a9SCarlo Caione return -ENOMEM; 2025b6c26a9SCarlo Caione 2035b6c26a9SCarlo Caione axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent); 2045b6c26a9SCarlo Caione axp20x = axp20x_pek->axp20x; 2055b6c26a9SCarlo Caione 2065b6c26a9SCarlo Caione axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR"); 2075b6c26a9SCarlo Caione if (axp20x_pek->irq_dbr < 0) { 2085b6c26a9SCarlo Caione dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n", 2095b6c26a9SCarlo Caione axp20x_pek->irq_dbr); 2105b6c26a9SCarlo Caione return axp20x_pek->irq_dbr; 2115b6c26a9SCarlo Caione } 2125b6c26a9SCarlo Caione axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc, 2135b6c26a9SCarlo Caione axp20x_pek->irq_dbr); 2145b6c26a9SCarlo Caione 2155b6c26a9SCarlo Caione axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF"); 2165b6c26a9SCarlo Caione if (axp20x_pek->irq_dbf < 0) { 2175b6c26a9SCarlo Caione dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n", 2185b6c26a9SCarlo Caione axp20x_pek->irq_dbf); 2195b6c26a9SCarlo Caione return axp20x_pek->irq_dbf; 2205b6c26a9SCarlo Caione } 2215b6c26a9SCarlo Caione axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc, 2225b6c26a9SCarlo Caione axp20x_pek->irq_dbf); 2235b6c26a9SCarlo Caione 2245b6c26a9SCarlo Caione axp20x_pek->input = devm_input_allocate_device(&pdev->dev); 2255b6c26a9SCarlo Caione if (!axp20x_pek->input) 2265b6c26a9SCarlo Caione return -ENOMEM; 2275b6c26a9SCarlo Caione 2285b6c26a9SCarlo Caione idev = axp20x_pek->input; 2295b6c26a9SCarlo Caione 2305b6c26a9SCarlo Caione idev->name = "axp20x-pek"; 2315b6c26a9SCarlo Caione idev->phys = "m1kbd/input2"; 2325b6c26a9SCarlo Caione idev->dev.parent = &pdev->dev; 2335b6c26a9SCarlo Caione 2345b6c26a9SCarlo Caione input_set_capability(idev, EV_KEY, KEY_POWER); 2355b6c26a9SCarlo Caione 2365b6c26a9SCarlo Caione input_set_drvdata(idev, axp20x_pek); 2375b6c26a9SCarlo Caione 2385b6c26a9SCarlo Caione error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr, 2395b6c26a9SCarlo Caione axp20x_pek_irq, 0, 2405b6c26a9SCarlo Caione "axp20x-pek-dbr", idev); 2415b6c26a9SCarlo Caione if (error < 0) { 2425b6c26a9SCarlo Caione dev_err(axp20x->dev, "Failed to request dbr IRQ#%d: %d\n", 2435b6c26a9SCarlo Caione axp20x_pek->irq_dbr, error); 2445b6c26a9SCarlo Caione return error; 2455b6c26a9SCarlo Caione } 2465b6c26a9SCarlo Caione 2475b6c26a9SCarlo Caione error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf, 2485b6c26a9SCarlo Caione axp20x_pek_irq, 0, 2495b6c26a9SCarlo Caione "axp20x-pek-dbf", idev); 2505b6c26a9SCarlo Caione if (error < 0) { 2515b6c26a9SCarlo Caione dev_err(axp20x->dev, "Failed to request dbf IRQ#%d: %d\n", 2525b6c26a9SCarlo Caione axp20x_pek->irq_dbf, error); 2535b6c26a9SCarlo Caione return error; 2545b6c26a9SCarlo Caione } 2555b6c26a9SCarlo Caione 256b388de88SDmitry Torokhov error = sysfs_create_group(&pdev->dev.kobj, &axp20x_attribute_group); 257b388de88SDmitry Torokhov if (error) { 258b388de88SDmitry Torokhov dev_err(axp20x->dev, "Failed to create sysfs attributes: %d\n", 259b388de88SDmitry Torokhov error); 2605b6c26a9SCarlo Caione return error; 261b388de88SDmitry Torokhov } 2625b6c26a9SCarlo Caione 263b388de88SDmitry Torokhov error = devm_add_action(&pdev->dev, 264b388de88SDmitry Torokhov axp20x_remove_sysfs_group, &pdev->dev); 265b388de88SDmitry Torokhov if (error) { 266b388de88SDmitry Torokhov axp20x_remove_sysfs_group(&pdev->dev); 267b388de88SDmitry Torokhov dev_err(&pdev->dev, "Failed to add sysfs cleanup action: %d\n", 268b388de88SDmitry Torokhov error); 269b388de88SDmitry Torokhov return error; 270b388de88SDmitry Torokhov } 2715b6c26a9SCarlo Caione 2725b6c26a9SCarlo Caione error = input_register_device(idev); 2735b6c26a9SCarlo Caione if (error) { 2745b6c26a9SCarlo Caione dev_err(axp20x->dev, "Can't register input device: %d\n", 2755b6c26a9SCarlo Caione error); 2765b6c26a9SCarlo Caione return error; 2775b6c26a9SCarlo Caione } 2785b6c26a9SCarlo Caione 279b388de88SDmitry Torokhov platform_set_drvdata(pdev, axp20x_pek); 2805b6c26a9SCarlo Caione 2815b6c26a9SCarlo Caione return 0; 2825b6c26a9SCarlo Caione } 2835b6c26a9SCarlo Caione 2845b6c26a9SCarlo Caione static struct platform_driver axp20x_pek_driver = { 2855b6c26a9SCarlo Caione .probe = axp20x_pek_probe, 2865b6c26a9SCarlo Caione .driver = { 2875b6c26a9SCarlo Caione .name = "axp20x-pek", 2885b6c26a9SCarlo Caione }, 2895b6c26a9SCarlo Caione }; 2905b6c26a9SCarlo Caione module_platform_driver(axp20x_pek_driver); 2915b6c26a9SCarlo Caione 2925b6c26a9SCarlo Caione MODULE_DESCRIPTION("axp20x Power Button"); 2935b6c26a9SCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 2945b6c26a9SCarlo Caione MODULE_LICENSE("GPL"); 295b6e26546SChen-Yu Tsai MODULE_ALIAS("platform:axp20x-pek"); 296