162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * wm831x-on.c - WM831X ON pin driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2009 Wolfson Microelectronics plc
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General
762306a36Sopenharmony_ci * Public License. See the file "COPYING" in the main directory of this
862306a36Sopenharmony_ci * archive for more details.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * This program is distributed in the hope that it will be useful,
1162306a36Sopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
1262306a36Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1362306a36Sopenharmony_ci * GNU General Public License for more details.
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * You should have received a copy of the GNU General Public License
1662306a36Sopenharmony_ci * along with this program; if not, write to the Free Software
1762306a36Sopenharmony_ci * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <linux/module.h>
2162306a36Sopenharmony_ci#include <linux/slab.h>
2262306a36Sopenharmony_ci#include <linux/kernel.h>
2362306a36Sopenharmony_ci#include <linux/errno.h>
2462306a36Sopenharmony_ci#include <linux/input.h>
2562306a36Sopenharmony_ci#include <linux/interrupt.h>
2662306a36Sopenharmony_ci#include <linux/platform_device.h>
2762306a36Sopenharmony_ci#include <linux/workqueue.h>
2862306a36Sopenharmony_ci#include <linux/mfd/wm831x/core.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct wm831x_on {
3162306a36Sopenharmony_ci	struct input_dev *dev;
3262306a36Sopenharmony_ci	struct delayed_work work;
3362306a36Sopenharmony_ci	struct wm831x *wm831x;
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/*
3762306a36Sopenharmony_ci * The chip gives us an interrupt when the ON pin is asserted but we
3862306a36Sopenharmony_ci * then need to poll to see when the pin is deasserted.
3962306a36Sopenharmony_ci */
4062306a36Sopenharmony_cistatic void wm831x_poll_on(struct work_struct *work)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	struct wm831x_on *wm831x_on = container_of(work, struct wm831x_on,
4362306a36Sopenharmony_ci						   work.work);
4462306a36Sopenharmony_ci	struct wm831x *wm831x = wm831x_on->wm831x;
4562306a36Sopenharmony_ci	int poll, ret;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	ret = wm831x_reg_read(wm831x, WM831X_ON_PIN_CONTROL);
4862306a36Sopenharmony_ci	if (ret >= 0) {
4962306a36Sopenharmony_ci		poll = !(ret & WM831X_ON_PIN_STS);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci		input_report_key(wm831x_on->dev, KEY_POWER, poll);
5262306a36Sopenharmony_ci		input_sync(wm831x_on->dev);
5362306a36Sopenharmony_ci	} else {
5462306a36Sopenharmony_ci		dev_err(wm831x->dev, "Failed to read ON status: %d\n", ret);
5562306a36Sopenharmony_ci		poll = 1;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if (poll)
5962306a36Sopenharmony_ci		schedule_delayed_work(&wm831x_on->work, 100);
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic irqreturn_t wm831x_on_irq(int irq, void *data)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct wm831x_on *wm831x_on = data;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	schedule_delayed_work(&wm831x_on->work, 0);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	return IRQ_HANDLED;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic int wm831x_on_probe(struct platform_device *pdev)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
7462306a36Sopenharmony_ci	struct wm831x_on *wm831x_on;
7562306a36Sopenharmony_ci	int irq = wm831x_irq(wm831x, platform_get_irq(pdev, 0));
7662306a36Sopenharmony_ci	int ret;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	wm831x_on = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_on),
7962306a36Sopenharmony_ci				 GFP_KERNEL);
8062306a36Sopenharmony_ci	if (!wm831x_on) {
8162306a36Sopenharmony_ci		dev_err(&pdev->dev, "Can't allocate data\n");
8262306a36Sopenharmony_ci		return -ENOMEM;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	wm831x_on->wm831x = wm831x;
8662306a36Sopenharmony_ci	INIT_DELAYED_WORK(&wm831x_on->work, wm831x_poll_on);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	wm831x_on->dev = devm_input_allocate_device(&pdev->dev);
8962306a36Sopenharmony_ci	if (!wm831x_on->dev) {
9062306a36Sopenharmony_ci		dev_err(&pdev->dev, "Can't allocate input dev\n");
9162306a36Sopenharmony_ci		ret = -ENOMEM;
9262306a36Sopenharmony_ci		goto err;
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	wm831x_on->dev->evbit[0] = BIT_MASK(EV_KEY);
9662306a36Sopenharmony_ci	wm831x_on->dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
9762306a36Sopenharmony_ci	wm831x_on->dev->name = "wm831x_on";
9862306a36Sopenharmony_ci	wm831x_on->dev->phys = "wm831x_on/input0";
9962306a36Sopenharmony_ci	wm831x_on->dev->dev.parent = &pdev->dev;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	ret = request_threaded_irq(irq, NULL, wm831x_on_irq,
10262306a36Sopenharmony_ci				   IRQF_TRIGGER_RISING | IRQF_ONESHOT,
10362306a36Sopenharmony_ci				   "wm831x_on",
10462306a36Sopenharmony_ci				   wm831x_on);
10562306a36Sopenharmony_ci	if (ret < 0) {
10662306a36Sopenharmony_ci		dev_err(&pdev->dev, "Unable to request IRQ: %d\n", ret);
10762306a36Sopenharmony_ci		goto err_input_dev;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci	ret = input_register_device(wm831x_on->dev);
11062306a36Sopenharmony_ci	if (ret) {
11162306a36Sopenharmony_ci		dev_dbg(&pdev->dev, "Can't register input device: %d\n", ret);
11262306a36Sopenharmony_ci		goto err_irq;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	platform_set_drvdata(pdev, wm831x_on);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return 0;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cierr_irq:
12062306a36Sopenharmony_ci	free_irq(irq, wm831x_on);
12162306a36Sopenharmony_cierr_input_dev:
12262306a36Sopenharmony_cierr:
12362306a36Sopenharmony_ci	return ret;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic int wm831x_on_remove(struct platform_device *pdev)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct wm831x_on *wm831x_on = platform_get_drvdata(pdev);
12962306a36Sopenharmony_ci	int irq = platform_get_irq(pdev, 0);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	free_irq(irq, wm831x_on);
13262306a36Sopenharmony_ci	cancel_delayed_work_sync(&wm831x_on->work);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return 0;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic struct platform_driver wm831x_on_driver = {
13862306a36Sopenharmony_ci	.probe		= wm831x_on_probe,
13962306a36Sopenharmony_ci	.remove		= wm831x_on_remove,
14062306a36Sopenharmony_ci	.driver		= {
14162306a36Sopenharmony_ci		.name	= "wm831x-on",
14262306a36Sopenharmony_ci	},
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_cimodule_platform_driver(wm831x_on_driver);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ciMODULE_ALIAS("platform:wm831x-on");
14762306a36Sopenharmony_ciMODULE_DESCRIPTION("WM831x ON pin");
14862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
14962306a36Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
15062306a36Sopenharmony_ci
151