/* * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved. * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include #include #include #include #include #include #include #include #define SNVS_LPSR_REG 0x4C /* LP Status Register */ #define SNVS_LPCR_REG 0x38 /* LP Control Register */ #define SNVS_HPSR_REG 0x14 #define SNVS_HPSR_BTN (0x1 << 6) #define SNVS_LPSR_SPO (0x1 << 18) #define SNVS_LPCR_DEP_EN (0x1 << 5) struct pwrkey_drv_data { void __iomem *ioaddr; int irq; int keycode; int keystate; /* 1:pressed */ int wakeup; struct timer_list check_timer; struct input_dev *input; }; static void imx_imx_snvs_check_for_events(unsigned long data) { struct pwrkey_drv_data *pdata = (struct pwrkey_drv_data *) data; struct input_dev *input = pdata->input; void __iomem *ioaddr = pdata->ioaddr; u32 state; state = ((readl_relaxed(ioaddr + SNVS_HPSR_REG) & SNVS_HPSR_BTN) ? 1 : 0); /* only report new event if status changed */ if (state ^ pdata->keystate) { pdata->keystate = state; input_event(input, EV_KEY, pdata->keycode, state); input_sync(input); } /* repeat check if pressed long */ if (state) { mod_timer(&pdata->check_timer, jiffies + msecs_to_jiffies(60)); } } static irqreturn_t imx_snvs_pwrkey_interrupt(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); void __iomem *ioaddr = pdata->ioaddr; u32 lp_status; lp_status = readl_relaxed(ioaddr + SNVS_LPSR_REG); if (lp_status & SNVS_LPSR_SPO) mod_timer(&pdata->check_timer, jiffies + msecs_to_jiffies(2)); /* clear SPO status */ writel_relaxed(lp_status, ioaddr + SNVS_LPSR_REG); return IRQ_HANDLED; } static int imx_snvs_pwrkey_probe(struct platform_device *pdev) { struct pwrkey_drv_data *pdata = NULL; struct input_dev *input = NULL; struct device_node *np; void __iomem *ioaddr; u32 val; int ret = 0; pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; /* Get SNVS register Page */ np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-snvs-pwrkey"); if (!np) return -ENODEV; pdata->ioaddr = of_iomap(np, 0); if (IS_ERR(pdata->ioaddr)) return PTR_ERR(pdata->ioaddr); if (of_property_read_u32(np, "fsl,keycode", &pdata->keycode)) { pdata->keycode = KEY_POWER; dev_warn(&pdev->dev, "KEY_POWER without setting in dts\n"); } pdata->wakeup = !!of_get_property(np, "fsl,wakeup", NULL); pdata->irq = platform_get_irq(pdev, 0); if (pdata->irq < 0) { dev_err(&pdev->dev, "no irq defined in platform data\n"); return -EINVAL; } ioaddr = pdata->ioaddr; val = readl_relaxed(ioaddr + SNVS_LPCR_REG); val |= SNVS_LPCR_DEP_EN, writel_relaxed(val, ioaddr + SNVS_LPCR_REG); /* clear the unexpected interrupt before driver ready */ val = readl_relaxed(ioaddr + SNVS_LPSR_REG); if (val & SNVS_LPSR_SPO) writel_relaxed(val | SNVS_LPSR_SPO, ioaddr + SNVS_LPSR_REG); setup_timer(&pdata->check_timer, imx_imx_snvs_check_for_events, (unsigned long) pdata); if (pdata->irq >= 0) { ret = devm_request_irq(&pdev->dev, pdata->irq, imx_snvs_pwrkey_interrupt, IRQF_TRIGGER_HIGH, pdev->name, pdev); if (ret) { dev_err(&pdev->dev, "interrupt not available.\n"); return ret; } } input = devm_input_allocate_device(&pdev->dev); if (!input) { dev_err(&pdev->dev, "failed to allocate the input device\n"); return -ENOMEM; } input->name = pdev->name; input->phys = "snvs-pwrkey/input0"; input->id.bustype = BUS_HOST; input->evbit[0] = BIT_MASK(EV_KEY); input_set_capability(input, EV_KEY, pdata->keycode); ret = input_register_device(input); if (ret < 0) { dev_err(&pdev->dev, "failed to register input device\n"); input_free_device(input); return ret; } pdata->input = input; platform_set_drvdata(pdev, pdata); device_init_wakeup(&pdev->dev, pdata->wakeup); dev_info(&pdev->dev, "i.MX snvs powerkey probed\n"); return 0; } static int imx_snvs_pwrkey_remove(struct platform_device *pdev) { struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); input_unregister_device(pdata->input); del_timer_sync(&pdata->check_timer); return 0; } static int imx_snvs_pwrkey_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); if (device_may_wakeup(&pdev->dev)) enable_irq_wake(pdata->irq); return 0; } static int imx_snvs_pwrkey_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); if (device_may_wakeup(&pdev->dev)) disable_irq_wake(pdata->irq); return 0; } static const struct of_device_id imx_snvs_pwrkey_ids[] = { { .compatible = "fsl,imx6sx-snvs-pwrkey" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx_snvs_pwrkey_ids); static SIMPLE_DEV_PM_OPS(imx_snvs_pwrkey_pm_ops, imx_snvs_pwrkey_suspend, imx_snvs_pwrkey_resume); static struct platform_driver imx_snvs_pwrkey_driver = { .driver = { .name = "snvs_pwrkey", .owner = THIS_MODULE, .pm = &imx_snvs_pwrkey_pm_ops, .of_match_table = imx_snvs_pwrkey_ids, }, .probe = imx_snvs_pwrkey_probe, .remove = imx_snvs_pwrkey_remove, }; module_platform_driver(imx_snvs_pwrkey_driver); MODULE_AUTHOR("Freescale Semiconductor"); MODULE_DESCRIPTION("i.MX snvs power key Driver"); MODULE_LICENSE("GPL");