| /* |
| * ASRMICRO PM80x EXTON2 driver |
| * |
| * Copyright: (C) Copyright 2024 ASR Microelectronics (Shanghai) Co., Ltd. |
| * |
| * This file is subject to the terms and conditions of the GNU General |
| * Public License. See the file "COPYING" in the main directory of this |
| * archive for more details. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/input.h> |
| #include <linux/mfd/88pm80x.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <linux/irq.h> |
| |
| struct pm80x_exton2_info { |
| struct input_dev *idev; |
| struct device *dev; |
| struct pm80x_chip *pm80x; |
| struct regmap *map; |
| int irq; |
| }; |
| |
| /* 88PM80x gives us an interrupt when EXTON2 is held */ |
| static irqreturn_t pm80x_exton2_handler(int irq, void *data) |
| { |
| struct pm80x_exton2_info *info = data; |
| int ret = 0; |
| unsigned int val; |
| |
| ret = regmap_read(info->map, PM800_STATUS_1, &val); |
| if (ret < 0) { |
| dev_err(info->idev->dev.parent, |
| "failed to read status: %d\n", ret); |
| return IRQ_NONE; |
| } |
| val &= PM800_CHG_STS1; |
| if (val) { |
| pm_stay_awake(info->dev); |
| } else { |
| pm_wakeup_event(info->dev, 8000); |
| } |
| |
| pr_info("pm80x_exton2 press = %x\n", !!val); /* sys-sanity */ |
| |
| input_report_key(info->idev, KEY_POWER, val); |
| input_sync(info->idev); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(pm80x_exton2_pm_ops, pm80x_dev_suspend, |
| pm80x_dev_resume); |
| |
| static int pm80x_exton2_dt_init(struct device_node *np, |
| struct pm80x_exton2_info *info) |
| { |
| return 0; |
| } |
| |
| static int pm80x_exton2_probe(struct platform_device *pdev) |
| { |
| struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent); |
| struct device_node *node = pdev->dev.of_node; |
| struct pm80x_exton2_info *info; |
| int err; |
| |
| if (CHIP_PM803 == chip->type || CHIP_PM801 == chip->type) |
| return -ENODEV; |
| |
| info = kzalloc(sizeof(struct pm80x_exton2_info), GFP_KERNEL); |
| if (!info) |
| return -ENOMEM; |
| |
| info->pm80x = chip; |
| |
| info->irq = platform_get_irq(pdev, 0); |
| if (info->irq < 0) { |
| dev_err(&pdev->dev, "No IRQ resource!\n"); |
| err = -EINVAL; |
| goto out; |
| } |
| |
| info->map = info->pm80x->regmap; |
| if (!info->map) { |
| dev_err(&pdev->dev, "no regmap!\n"); |
| err = -EINVAL; |
| goto out; |
| } |
| |
| info->idev = input_allocate_device(); |
| if (!info->idev) { |
| dev_err(&pdev->dev, "Failed to allocate input dev\n"); |
| err = -ENOMEM; |
| goto out; |
| } |
| |
| info->dev = &pdev->dev; |
| info->idev->name = "88pm80x_exton2"; |
| info->idev->phys = "88pm80x_exton2/input0"; |
| info->idev->id.bustype = BUS_I2C; |
| info->idev->dev.parent = &pdev->dev; |
| info->idev->evbit[0] = BIT_MASK(EV_KEY); |
| __set_bit(KEY_POWER, info->idev->keybit); |
| |
| err = pm80x_exton2_dt_init(node, info); |
| if (err < 0) { |
| err = -ENODEV; |
| goto out_reg; |
| } |
| |
| irq_set_status_flags(info->irq, IRQ_NOAUTOEN); |
| err = pm80x_request_irq(info->pm80x, info->irq, pm80x_exton2_handler, |
| IRQF_ONESHOT | IRQF_NO_SUSPEND, |
| "exton2", info); |
| if (err < 0) { |
| dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n", |
| info->irq, err); |
| goto out_reg; |
| } |
| |
| err = input_register_device(info->idev); |
| if (err) { |
| dev_err(&pdev->dev, "Can't register input device: %d\n", err); |
| goto out_irq; |
| } |
| |
| if (CHIP_PM813S_A0_ID == chip->chip_id || |
| CHIP_PM813S_A1_ID == chip->chip_id) |
| regmap_update_bits(info->map, PM800_RTC_MISC4, 0xf0, 0x70); |
| else |
| regmap_update_bits(info->map, PM800_RTC_MISC4, 0xf0, 0xf0); |
| |
| platform_set_drvdata(pdev, info); |
| |
| /* set FAULT_WU_EN */ |
| if (CHIP_PM803 == chip->type || |
| CHIP_PM813 == chip->type) |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM803_FAULT_WAKEUP_EN, |
| PM803_FAULT_WAKEUP_EN); |
| else |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM800_FAULT_WAKEUP_EN, |
| PM800_FAULT_WAKEUP_EN); |
| |
| /* need to set fault_wakeup for asr new pmics */ |
| if (CHIP_PM802 == chip->type || |
| CHIP_PM813_ID == chip->chip_id) |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM800_FAULT_WAKEUP, |
| PM800_FAULT_WAKEUP); |
| else if (CHIP_PM803 == chip->type || |
| CHIP_PM813S_A0_ID == chip->chip_id || |
| CHIP_PM813S_A1_ID == chip->chip_id) |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM803_FAULT_WAKEUP, |
| PM803_FAULT_WAKEUP); |
| |
| /* OnKey runs timer => need wake-constraint {Press ... Release+mSec} |
| * (especially to avoid false-panic-emulation). |
| * device_init_wakeup() creates "88pm80x-ext2key" |
| * but pm_stay_awake(&info->idev->dev) doesn't as required |
| * Do NOT use the driver's name but the "exton2" instead. |
| */ |
| device_init_wakeup(info->dev, true); |
| enable_irq(info->irq); |
| return 0; |
| |
| out_irq: |
| pm80x_free_irq(info->pm80x, info->irq, info); |
| out_reg: |
| input_free_device(info->idev); |
| out: |
| kfree(info); |
| return err; |
| } |
| |
| static int pm80x_exton2_remove(struct platform_device *pdev) |
| { |
| struct pm80x_exton2_info *info = platform_get_drvdata(pdev); |
| |
| disable_irq(info->irq); |
| pm80x_free_irq(info->pm80x, info->irq, info); |
| input_unregister_device(info->idev); |
| kfree(info); |
| return 0; |
| } |
| |
| static void pm80x_exton2_shutdown(struct platform_device *pdev) |
| { |
| struct pm80x_exton2_info *info = platform_get_drvdata(pdev); |
| disable_irq(info->irq); |
| pm80x_free_irq(info->pm80x, info->irq, info); |
| return; |
| } |
| |
| static const struct of_device_id pm80x_exton2_dt_match[] = { |
| { .compatible = "88pm80x-ext2key", }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, pm80x_exton2_dt_match); |
| |
| static struct platform_driver pm80x_exton2_driver = { |
| .driver = { |
| .name = "88pm80x-ext2key", |
| .owner = THIS_MODULE, |
| .pm = &pm80x_exton2_pm_ops, |
| .of_match_table = of_match_ptr(pm80x_exton2_dt_match), |
| }, |
| .probe = pm80x_exton2_probe, |
| .remove = pm80x_exton2_remove, |
| .shutdown = pm80x_exton2_shutdown, |
| }; |
| |
| module_platform_driver(pm80x_exton2_driver); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("ASRMICRO PM80x EXTON2 driver"); |
| MODULE_AUTHOR("Xuhong Gao <xuhonggao@asrmicro.com>"); |
| MODULE_ALIAS("platform:pm80x-exton2"); |