/* * drivers/power/axp/axp2101/axp2101-powerkey.c * (C) Copyright 2010-2016 * Allwinner Technology Co., Ltd. * Pannan * * powerkey driver of axp2101 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../axp-core.h" #include "../axp-powerkey.h" #include "axp2101.h" static int axp2101_powerkey_probe(struct platform_device *pdev) { struct axp_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); struct axp_powerkey_info *info; struct input_dev *powerkey_dev; int err = 0, i, irq, ret; struct axp_regmap *map = axp_dev->regmap; u8 val; if (pdev->dev.of_node) { /* get dt and sysconfig */ ret = axp_powerkey_dt_parse(pdev->dev.of_node, &axp2101_config); if (ret) { pr_err("%s parse device tree err\n", __func__); return -EINVAL; } } else { pr_err("axp2101 powerkey device tree err!\n"); return -EBUSY; } info = kzalloc(sizeof(struct axp_powerkey_info), GFP_KERNEL); if (!info) return -ENOMEM; info->chip = axp_dev; /* register input device */ powerkey_dev = input_allocate_device(); if (!powerkey_dev) { pr_err("alloc powerkey input device error\n"); goto out; } powerkey_dev->name = pdev->name; powerkey_dev->phys = "m1kbd/input2"; powerkey_dev->id.bustype = BUS_HOST; powerkey_dev->id.vendor = 0x0001; powerkey_dev->id.product = 0x0001; powerkey_dev->id.version = 0x0100; powerkey_dev->open = NULL; powerkey_dev->close = NULL; powerkey_dev->dev.parent = &pdev->dev; set_bit(EV_KEY, powerkey_dev->evbit); set_bit(EV_REL, powerkey_dev->evbit); set_bit(EV_REP, powerkey_dev->evbit); set_bit(KEY_POWER, powerkey_dev->keybit); err = input_register_device(powerkey_dev); if (err) { pr_err("Unable to Register the axp_powerkey\n"); goto out_reg; } info->idev = powerkey_dev; for (i = 0; i < ARRAY_SIZE(axp_powerkey_irq); i++) { irq = platform_get_irq_byname(pdev, axp_powerkey_irq[i].name); if (irq < 0) continue; err = axp_request_irq(axp_dev, irq, axp_powerkey_irq[i].isr, info); if (err != 0) { dev_err(&pdev->dev, "failed to request %s IRQ %d: %d\n" , axp_powerkey_irq[i].name, irq, err); goto out_irq; } dev_dbg(&pdev->dev, "Requested %s IRQ %d: %d\n", axp_powerkey_irq[i].name, irq, err); } platform_set_drvdata(pdev, info); axp_regmap_read(map, axp2101_PONLEVEL, &val); if (axp2101_config.pmu_powkey_on_time < 128) val &= 0x3C; else if (axp2101_config.pmu_powkey_on_time < 512) { val &= 0x3C; val |= 0x01; } else if (axp2101_config.pmu_powkey_on_time < 1000) { val &= 0x3C; val |= 0x10; } else { val &= 0x3C; val |= 0x11; } axp_regmap_write(map, axp2101_PONLEVEL, val); /* pok long time set*/ if (axp2101_config.pmu_powkey_long_time < 1000) axp2101_config.pmu_powkey_long_time = 1000; if (axp2101_config.pmu_powkey_long_time > 2500) axp2101_config.pmu_powkey_long_time = 2500; axp_regmap_read(map, axp2101_PONLEVEL, &val); val &= 0xcf; val |= (((axp2101_config.pmu_powkey_long_time - 1000) / 500) << 4); axp_regmap_write(map, axp2101_PONLEVEL, val); /* pek offlevel poweroff en set*/ if (axp2101_config.pmu_powkey_off_en) axp2101_config.pmu_powkey_off_en = 1; else axp2101_config.pmu_powkey_off_en = 0; axp_regmap_read(map, axp2101_PWROFF_EN, &val); val &= 0x0D; val |= (axp2101_config.pmu_powkey_off_en << 1); axp_regmap_write(map, axp2101_PWROFF_EN, val); /*Init offlevel restart or not */ if (axp2101_config.pmu_powkey_off_func) axp_regmap_set_bits(map, axp2101_PWROFF_EN, 0x01); /* restart */ else axp_regmap_clr_bits(map, axp2101_PWROFF_EN, 0x01); /* not restart*/ /* pek delay set */ axp_regmap_read(map, axp2101_PWR_TIME_CTRL, &val); val &= 0xfc; if (axp2101_config.pmu_pwrok_time < 32) val |= ((axp2101_config.pmu_pwrok_time / 8) - 1); else val |= ((axp2101_config.pmu_pwrok_time / 32) + 1); axp_regmap_write(map, axp2101_PWR_TIME_CTRL, val); /* pek offlevel time set */ if (axp2101_config.pmu_powkey_off_time < 4000) axp2101_config.pmu_powkey_off_time = 4000; if (axp2101_config.pmu_powkey_off_time > 10000) axp2101_config.pmu_powkey_off_time = 10000; axp_regmap_read(map, axp2101_PONLEVEL, &val); val &= 0x33; val |= ((axp2101_config.pmu_powkey_off_time - 4000) / 2000 << 2); axp_regmap_write(map, axp2101_PONLEVEL, val); /* vbat use all channels */ axp_regmap_write(map, axp2101_VBAT_H, 0x40); return 0; out_irq: for (i = i - 1; i >= 0; i--) { irq = platform_get_irq_byname(pdev, axp_powerkey_irq[i].name); if (irq < 0) continue; axp_free_irq(axp_dev, irq); } out_reg: input_free_device(powerkey_dev); out: kfree(info); return err; } static int axp2101_powerkey_remove(struct platform_device *pdev) { int i, irq; struct axp_powerkey_info *info = platform_get_drvdata(pdev); struct axp_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); for (i = 0; i < ARRAY_SIZE(axp_powerkey_irq); i++) { irq = platform_get_irq_byname(pdev, axp_powerkey_irq[i].name); if (irq < 0) continue; axp_free_irq(axp_dev, irq); } input_unregister_device(info->idev); kfree(info); return 0; } static int axp2101_powerkey_suspend(struct device *dev) { int i, irq; struct platform_device *pdev = to_platform_device(dev); struct axp_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); u32 powkey_wakeup_irq = axp2101_config.pmu_powkey_wakeup_irq; /* invalid powkey_wakeup_irq, keep default setting, do nothing */ if (powkey_wakeup_irq == -1) return 0; for (i = 0; i < ARRAY_SIZE(axp_powerkey_irq); i++) { irq = platform_get_irq_byname(pdev, axp_powerkey_irq[i].name); if (irq < 0) continue; /* disable non-wakeup-source irq when suspend */ if (!((powkey_wakeup_irq >> i) & 0x1)) axp_disable_irq(axp_dev, irq); } return 0; } static int axp2101_powerkey_resume(struct device *dev) { int i, irq; struct platform_device *pdev = to_platform_device(dev); struct axp_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); u32 powkey_wakeup_irq = axp2101_config.pmu_powkey_wakeup_irq; for (i = 0; i < ARRAY_SIZE(axp_powerkey_irq); i++) { irq = platform_get_irq_byname(pdev, axp_powerkey_irq[i].name); if (irq < 0) continue; /* enable non-wakeup-source irq for recovery when resume */ if (!((powkey_wakeup_irq >> i) & 0x1)) axp_enable_irq(axp_dev, irq); } return 0; } static int axp2101_powerkey_resume_noirq(struct device *dev) { u8 val; struct platform_device *pdev = to_platform_device(dev); struct axp_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); struct axp_powerkey_info *info = platform_get_drvdata(pdev); struct axp_regmap *map = axp_dev->regmap; u32 powkey_wakeup_irq = axp2101_config.pmu_powkey_wakeup_irq; /* * power key exception handle: * if both positive and negative irq are pending, * we regard this as an exception because of unknown key status. */ axp_regmap_read(map, axp2101_INTSTS2, &val); if ((val & 0x03) == 0x03) { /* clear power key positive and negative irq flag bits, other bits reserve */ axp_regmap_write(map, axp2101_INTSTS2, 0x03); /* * we must keep the sequence of power key report as follow, * otherwise the key down remian may lead to errors when resume complete */ input_report_key(info->idev, KEY_POWER, 1); /* key down */ input_sync(info->idev); input_report_key(info->idev, KEY_POWER, 0); /* key up */ input_sync(info->idev); } /* invalid powkey_wakeup_irq, keep default setting, do nothing */ if (powkey_wakeup_irq == -1) return 0; return 0; } static const struct dev_pm_ops axp2101_powerkey_pm_ops = { .suspend = axp2101_powerkey_suspend, .resume_noirq = axp2101_powerkey_resume_noirq, .resume = axp2101_powerkey_resume, }; static const struct of_device_id axp2101_powerkey_dt_ids[] = { { .compatible = "axp2101-powerkey", }, {}, }; MODULE_DEVICE_TABLE(of, axp2101_powerkey_dt_ids); static struct platform_driver axp2101_powerkey_driver = { .driver = { .name = "axp2101-powerkey", .pm = &axp2101_powerkey_pm_ops, .of_match_table = axp2101_powerkey_dt_ids, }, .probe = axp2101_powerkey_probe, .remove = axp2101_powerkey_remove, }; module_platform_driver(axp2101_powerkey_driver); MODULE_DESCRIPTION("Powerkey Driver of AXP2101"); MODULE_AUTHOR("pannan"); MODULE_LICENSE("GPL");