aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/input/keyboard/snvs_pwrkey.c
blob: 1d6d62c4e663131cb1c715f672f16fa8237b9b10 (plain) (tree)























































































                                                                              
                




















                                                                           






                                                                         






                                                                            



































































































                                                                             
/*
 * 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 <linux/device.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>

#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");