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