18c2ecf20Sopenharmony_ci/**
28c2ecf20Sopenharmony_ci * wm831x-on.c - WM831X ON pin driver
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2009 Wolfson Microelectronics plc
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General
78c2ecf20Sopenharmony_ci * Public License. See the file "COPYING" in the main directory of this
88c2ecf20Sopenharmony_ci * archive for more details.
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * This program is distributed in the hope that it will be useful,
118c2ecf20Sopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
128c2ecf20Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
138c2ecf20Sopenharmony_ci * GNU General Public License for more details.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * You should have received a copy of the GNU General Public License
168c2ecf20Sopenharmony_ci * along with this program; if not, write to the Free Software
178c2ecf20Sopenharmony_ci * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
188c2ecf20Sopenharmony_ci */
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <linux/module.h>
218c2ecf20Sopenharmony_ci#include <linux/slab.h>
228c2ecf20Sopenharmony_ci#include <linux/kernel.h>
238c2ecf20Sopenharmony_ci#include <linux/errno.h>
248c2ecf20Sopenharmony_ci#include <linux/input.h>
258c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
268c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
278c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
288c2ecf20Sopenharmony_ci#include <linux/mfd/wm831x/core.h>
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistruct wm831x_on {
318c2ecf20Sopenharmony_ci	struct input_dev *dev;
328c2ecf20Sopenharmony_ci	struct delayed_work work;
338c2ecf20Sopenharmony_ci	struct wm831x *wm831x;
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci/*
378c2ecf20Sopenharmony_ci * The chip gives us an interrupt when the ON pin is asserted but we
388c2ecf20Sopenharmony_ci * then need to poll to see when the pin is deasserted.
398c2ecf20Sopenharmony_ci */
408c2ecf20Sopenharmony_cistatic void wm831x_poll_on(struct work_struct *work)
418c2ecf20Sopenharmony_ci{
428c2ecf20Sopenharmony_ci	struct wm831x_on *wm831x_on = container_of(work, struct wm831x_on,
438c2ecf20Sopenharmony_ci						   work.work);
448c2ecf20Sopenharmony_ci	struct wm831x *wm831x = wm831x_on->wm831x;
458c2ecf20Sopenharmony_ci	int poll, ret;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	ret = wm831x_reg_read(wm831x, WM831X_ON_PIN_CONTROL);
488c2ecf20Sopenharmony_ci	if (ret >= 0) {
498c2ecf20Sopenharmony_ci		poll = !(ret & WM831X_ON_PIN_STS);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci		input_report_key(wm831x_on->dev, KEY_POWER, poll);
528c2ecf20Sopenharmony_ci		input_sync(wm831x_on->dev);
538c2ecf20Sopenharmony_ci	} else {
548c2ecf20Sopenharmony_ci		dev_err(wm831x->dev, "Failed to read ON status: %d\n", ret);
558c2ecf20Sopenharmony_ci		poll = 1;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	if (poll)
598c2ecf20Sopenharmony_ci		schedule_delayed_work(&wm831x_on->work, 100);
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic irqreturn_t wm831x_on_irq(int irq, void *data)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	struct wm831x_on *wm831x_on = data;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	schedule_delayed_work(&wm831x_on->work, 0);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic int wm831x_on_probe(struct platform_device *pdev)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
748c2ecf20Sopenharmony_ci	struct wm831x_on *wm831x_on;
758c2ecf20Sopenharmony_ci	int irq = wm831x_irq(wm831x, platform_get_irq(pdev, 0));
768c2ecf20Sopenharmony_ci	int ret;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	wm831x_on = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_on),
798c2ecf20Sopenharmony_ci				 GFP_KERNEL);
808c2ecf20Sopenharmony_ci	if (!wm831x_on) {
818c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Can't allocate data\n");
828c2ecf20Sopenharmony_ci		return -ENOMEM;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	wm831x_on->wm831x = wm831x;
868c2ecf20Sopenharmony_ci	INIT_DELAYED_WORK(&wm831x_on->work, wm831x_poll_on);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	wm831x_on->dev = devm_input_allocate_device(&pdev->dev);
898c2ecf20Sopenharmony_ci	if (!wm831x_on->dev) {
908c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Can't allocate input dev\n");
918c2ecf20Sopenharmony_ci		ret = -ENOMEM;
928c2ecf20Sopenharmony_ci		goto err;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	wm831x_on->dev->evbit[0] = BIT_MASK(EV_KEY);
968c2ecf20Sopenharmony_ci	wm831x_on->dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
978c2ecf20Sopenharmony_ci	wm831x_on->dev->name = "wm831x_on";
988c2ecf20Sopenharmony_ci	wm831x_on->dev->phys = "wm831x_on/input0";
998c2ecf20Sopenharmony_ci	wm831x_on->dev->dev.parent = &pdev->dev;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	ret = request_threaded_irq(irq, NULL, wm831x_on_irq,
1028c2ecf20Sopenharmony_ci				   IRQF_TRIGGER_RISING | IRQF_ONESHOT,
1038c2ecf20Sopenharmony_ci				   "wm831x_on",
1048c2ecf20Sopenharmony_ci				   wm831x_on);
1058c2ecf20Sopenharmony_ci	if (ret < 0) {
1068c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unable to request IRQ: %d\n", ret);
1078c2ecf20Sopenharmony_ci		goto err_input_dev;
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci	ret = input_register_device(wm831x_on->dev);
1108c2ecf20Sopenharmony_ci	if (ret) {
1118c2ecf20Sopenharmony_ci		dev_dbg(&pdev->dev, "Can't register input device: %d\n", ret);
1128c2ecf20Sopenharmony_ci		goto err_irq;
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, wm831x_on);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	return 0;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cierr_irq:
1208c2ecf20Sopenharmony_ci	free_irq(irq, wm831x_on);
1218c2ecf20Sopenharmony_cierr_input_dev:
1228c2ecf20Sopenharmony_cierr:
1238c2ecf20Sopenharmony_ci	return ret;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic int wm831x_on_remove(struct platform_device *pdev)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	struct wm831x_on *wm831x_on = platform_get_drvdata(pdev);
1298c2ecf20Sopenharmony_ci	int irq = platform_get_irq(pdev, 0);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	free_irq(irq, wm831x_on);
1328c2ecf20Sopenharmony_ci	cancel_delayed_work_sync(&wm831x_on->work);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	return 0;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic struct platform_driver wm831x_on_driver = {
1388c2ecf20Sopenharmony_ci	.probe		= wm831x_on_probe,
1398c2ecf20Sopenharmony_ci	.remove		= wm831x_on_remove,
1408c2ecf20Sopenharmony_ci	.driver		= {
1418c2ecf20Sopenharmony_ci		.name	= "wm831x-on",
1428c2ecf20Sopenharmony_ci	},
1438c2ecf20Sopenharmony_ci};
1448c2ecf20Sopenharmony_cimodule_platform_driver(wm831x_on_driver);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:wm831x-on");
1478c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("WM831x ON pin");
1488c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1498c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
1508c2ecf20Sopenharmony_ci
151