mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
f6f14e4a33
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new() which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Link: https://lore.kernel.org/r/20230920125829.1478827-48-u.kleine-koenig@pengutronix.de Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
253 lines
6.3 KiB
C
253 lines
6.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Driver for Motorola PCAP2 touchscreen as found in the EZX phone platform.
|
|
*
|
|
* Copyright (C) 2006 Harald Welte <laforge@openezx.org>
|
|
* Copyright (C) 2009 Daniel Ribeiro <drwyrm@gmail.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/input.h>
|
|
#include <linux/mfd/ezx-pcap.h>
|
|
|
|
struct pcap_ts {
|
|
struct pcap_chip *pcap;
|
|
struct input_dev *input;
|
|
struct delayed_work work;
|
|
u16 x, y;
|
|
u16 pressure;
|
|
u8 read_state;
|
|
};
|
|
|
|
#define SAMPLE_DELAY 20 /* msecs */
|
|
|
|
#define X_AXIS_MIN 0
|
|
#define X_AXIS_MAX 1023
|
|
#define Y_AXIS_MAX X_AXIS_MAX
|
|
#define Y_AXIS_MIN X_AXIS_MIN
|
|
#define PRESSURE_MAX X_AXIS_MAX
|
|
#define PRESSURE_MIN X_AXIS_MIN
|
|
|
|
static void pcap_ts_read_xy(void *data, u16 res[2])
|
|
{
|
|
struct pcap_ts *pcap_ts = data;
|
|
|
|
switch (pcap_ts->read_state) {
|
|
case PCAP_ADC_TS_M_PRESSURE:
|
|
/* pressure reading is unreliable */
|
|
if (res[0] > PRESSURE_MIN && res[0] < PRESSURE_MAX)
|
|
pcap_ts->pressure = res[0];
|
|
pcap_ts->read_state = PCAP_ADC_TS_M_XY;
|
|
schedule_delayed_work(&pcap_ts->work, 0);
|
|
break;
|
|
case PCAP_ADC_TS_M_XY:
|
|
pcap_ts->y = res[0];
|
|
pcap_ts->x = res[1];
|
|
if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX ||
|
|
pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) {
|
|
/* pen has been released */
|
|
input_report_abs(pcap_ts->input, ABS_PRESSURE, 0);
|
|
input_report_key(pcap_ts->input, BTN_TOUCH, 0);
|
|
|
|
pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
|
|
schedule_delayed_work(&pcap_ts->work, 0);
|
|
} else {
|
|
/* pen is touching the screen */
|
|
input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x);
|
|
input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y);
|
|
input_report_key(pcap_ts->input, BTN_TOUCH, 1);
|
|
input_report_abs(pcap_ts->input, ABS_PRESSURE,
|
|
pcap_ts->pressure);
|
|
|
|
/* switch back to pressure read mode */
|
|
pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
|
|
schedule_delayed_work(&pcap_ts->work,
|
|
msecs_to_jiffies(SAMPLE_DELAY));
|
|
}
|
|
input_sync(pcap_ts->input);
|
|
break;
|
|
default:
|
|
dev_warn(&pcap_ts->input->dev,
|
|
"pcap_ts: Warning, unhandled read_state %d\n",
|
|
pcap_ts->read_state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void pcap_ts_work(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dw = to_delayed_work(work);
|
|
struct pcap_ts *pcap_ts = container_of(dw, struct pcap_ts, work);
|
|
u8 ch[2];
|
|
|
|
pcap_set_ts_bits(pcap_ts->pcap,
|
|
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
|
|
|
|
if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY)
|
|
return;
|
|
|
|
/* start adc conversion */
|
|
ch[0] = PCAP_ADC_CH_TS_X1;
|
|
ch[1] = PCAP_ADC_CH_TS_Y1;
|
|
pcap_adc_async(pcap_ts->pcap, PCAP_ADC_BANK_1, 0, ch,
|
|
pcap_ts_read_xy, pcap_ts);
|
|
}
|
|
|
|
static irqreturn_t pcap_ts_event_touch(int pirq, void *data)
|
|
{
|
|
struct pcap_ts *pcap_ts = data;
|
|
|
|
if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) {
|
|
pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
|
|
schedule_delayed_work(&pcap_ts->work, 0);
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int pcap_ts_open(struct input_dev *dev)
|
|
{
|
|
struct pcap_ts *pcap_ts = input_get_drvdata(dev);
|
|
|
|
pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
|
|
schedule_delayed_work(&pcap_ts->work, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pcap_ts_close(struct input_dev *dev)
|
|
{
|
|
struct pcap_ts *pcap_ts = input_get_drvdata(dev);
|
|
|
|
cancel_delayed_work_sync(&pcap_ts->work);
|
|
|
|
pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
|
|
pcap_set_ts_bits(pcap_ts->pcap,
|
|
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
|
|
}
|
|
|
|
static int pcap_ts_probe(struct platform_device *pdev)
|
|
{
|
|
struct input_dev *input_dev;
|
|
struct pcap_ts *pcap_ts;
|
|
int err = -ENOMEM;
|
|
|
|
pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
|
|
if (!pcap_ts)
|
|
return err;
|
|
|
|
pcap_ts->pcap = dev_get_drvdata(pdev->dev.parent);
|
|
platform_set_drvdata(pdev, pcap_ts);
|
|
|
|
input_dev = input_allocate_device();
|
|
if (!input_dev)
|
|
goto fail;
|
|
|
|
INIT_DELAYED_WORK(&pcap_ts->work, pcap_ts_work);
|
|
|
|
pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
|
|
pcap_set_ts_bits(pcap_ts->pcap,
|
|
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
|
|
|
|
pcap_ts->input = input_dev;
|
|
input_set_drvdata(input_dev, pcap_ts);
|
|
|
|
input_dev->name = "pcap-touchscreen";
|
|
input_dev->phys = "pcap_ts/input0";
|
|
input_dev->id.bustype = BUS_HOST;
|
|
input_dev->id.vendor = 0x0001;
|
|
input_dev->id.product = 0x0002;
|
|
input_dev->id.version = 0x0100;
|
|
input_dev->dev.parent = &pdev->dev;
|
|
input_dev->open = pcap_ts_open;
|
|
input_dev->close = pcap_ts_close;
|
|
|
|
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
|
|
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
|
|
input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
|
|
PRESSURE_MAX, 0, 0);
|
|
|
|
err = input_register_device(pcap_ts->input);
|
|
if (err)
|
|
goto fail_allocate;
|
|
|
|
err = request_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS),
|
|
pcap_ts_event_touch, 0, "Touch Screen", pcap_ts);
|
|
if (err)
|
|
goto fail_register;
|
|
|
|
return 0;
|
|
|
|
fail_register:
|
|
input_unregister_device(input_dev);
|
|
goto fail;
|
|
fail_allocate:
|
|
input_free_device(input_dev);
|
|
fail:
|
|
kfree(pcap_ts);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void pcap_ts_remove(struct platform_device *pdev)
|
|
{
|
|
struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
|
|
|
|
free_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), pcap_ts);
|
|
cancel_delayed_work_sync(&pcap_ts->work);
|
|
|
|
input_unregister_device(pcap_ts->input);
|
|
|
|
kfree(pcap_ts);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int pcap_ts_suspend(struct device *dev)
|
|
{
|
|
struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
|
|
|
|
pcap_set_ts_bits(pcap_ts->pcap, PCAP_ADC_TS_REF_LOWPWR);
|
|
return 0;
|
|
}
|
|
|
|
static int pcap_ts_resume(struct device *dev)
|
|
{
|
|
struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
|
|
|
|
pcap_set_ts_bits(pcap_ts->pcap,
|
|
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops pcap_ts_pm_ops = {
|
|
.suspend = pcap_ts_suspend,
|
|
.resume = pcap_ts_resume,
|
|
};
|
|
#define PCAP_TS_PM_OPS (&pcap_ts_pm_ops)
|
|
#else
|
|
#define PCAP_TS_PM_OPS NULL
|
|
#endif
|
|
|
|
static struct platform_driver pcap_ts_driver = {
|
|
.probe = pcap_ts_probe,
|
|
.remove_new = pcap_ts_remove,
|
|
.driver = {
|
|
.name = "pcap-ts",
|
|
.pm = PCAP_TS_PM_OPS,
|
|
},
|
|
};
|
|
module_platform_driver(pcap_ts_driver);
|
|
|
|
MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver");
|
|
MODULE_AUTHOR("Daniel Ribeiro / Harald Welte");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:pcap_ts");
|