12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
200cfa730SMark Brown /*
300cfa730SMark Brown * Touchscreen driver for WM831x PMICs
400cfa730SMark Brown *
500cfa730SMark Brown * Copyright 2011 Wolfson Microelectronics plc.
600cfa730SMark Brown * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
700cfa730SMark Brown */
800cfa730SMark Brown
900cfa730SMark Brown #include <linux/module.h>
1000cfa730SMark Brown #include <linux/moduleparam.h>
1100cfa730SMark Brown #include <linux/kernel.h>
1200cfa730SMark Brown #include <linux/string.h>
1300cfa730SMark Brown #include <linux/pm.h>
1400cfa730SMark Brown #include <linux/input.h>
1500cfa730SMark Brown #include <linux/interrupt.h>
1600cfa730SMark Brown #include <linux/io.h>
1700cfa730SMark Brown #include <linux/mfd/wm831x/core.h>
1800cfa730SMark Brown #include <linux/mfd/wm831x/irq.h>
1900cfa730SMark Brown #include <linux/mfd/wm831x/pdata.h>
2000cfa730SMark Brown #include <linux/platform_device.h>
2100cfa730SMark Brown #include <linux/slab.h>
2200cfa730SMark Brown #include <linux/types.h>
2300cfa730SMark Brown
2400cfa730SMark Brown /*
2500cfa730SMark Brown * R16424 (0x4028) - Touch Control 1
2600cfa730SMark Brown */
2700cfa730SMark Brown #define WM831X_TCH_ENA 0x8000 /* TCH_ENA */
2800cfa730SMark Brown #define WM831X_TCH_CVT_ENA 0x4000 /* TCH_CVT_ENA */
2900cfa730SMark Brown #define WM831X_TCH_SLPENA 0x1000 /* TCH_SLPENA */
3000cfa730SMark Brown #define WM831X_TCH_Z_ENA 0x0400 /* TCH_Z_ENA */
3100cfa730SMark Brown #define WM831X_TCH_Y_ENA 0x0200 /* TCH_Y_ENA */
3200cfa730SMark Brown #define WM831X_TCH_X_ENA 0x0100 /* TCH_X_ENA */
3300cfa730SMark Brown #define WM831X_TCH_DELAY_MASK 0x00E0 /* TCH_DELAY - [7:5] */
3400cfa730SMark Brown #define WM831X_TCH_DELAY_SHIFT 5 /* TCH_DELAY - [7:5] */
3500cfa730SMark Brown #define WM831X_TCH_DELAY_WIDTH 3 /* TCH_DELAY - [7:5] */
3600cfa730SMark Brown #define WM831X_TCH_RATE_MASK 0x001F /* TCH_RATE - [4:0] */
3700cfa730SMark Brown #define WM831X_TCH_RATE_SHIFT 0 /* TCH_RATE - [4:0] */
3800cfa730SMark Brown #define WM831X_TCH_RATE_WIDTH 5 /* TCH_RATE - [4:0] */
3900cfa730SMark Brown
4000cfa730SMark Brown /*
4100cfa730SMark Brown * R16425 (0x4029) - Touch Control 2
4200cfa730SMark Brown */
4300cfa730SMark Brown #define WM831X_TCH_PD_WK 0x2000 /* TCH_PD_WK */
4400cfa730SMark Brown #define WM831X_TCH_5WIRE 0x1000 /* TCH_5WIRE */
4500cfa730SMark Brown #define WM831X_TCH_PDONLY 0x0800 /* TCH_PDONLY */
4600cfa730SMark Brown #define WM831X_TCH_ISEL 0x0100 /* TCH_ISEL */
4700cfa730SMark Brown #define WM831X_TCH_RPU_MASK 0x000F /* TCH_RPU - [3:0] */
4800cfa730SMark Brown #define WM831X_TCH_RPU_SHIFT 0 /* TCH_RPU - [3:0] */
4900cfa730SMark Brown #define WM831X_TCH_RPU_WIDTH 4 /* TCH_RPU - [3:0] */
5000cfa730SMark Brown
5100cfa730SMark Brown /*
5200cfa730SMark Brown * R16426-8 (0x402A-C) - Touch Data X/Y/X
5300cfa730SMark Brown */
5400cfa730SMark Brown #define WM831X_TCH_PD 0x8000 /* TCH_PD1 */
5500cfa730SMark Brown #define WM831X_TCH_DATA_MASK 0x0FFF /* TCH_DATA - [11:0] */
5600cfa730SMark Brown #define WM831X_TCH_DATA_SHIFT 0 /* TCH_DATA - [11:0] */
5700cfa730SMark Brown #define WM831X_TCH_DATA_WIDTH 12 /* TCH_DATA - [11:0] */
5800cfa730SMark Brown
5900cfa730SMark Brown struct wm831x_ts {
6000cfa730SMark Brown struct input_dev *input_dev;
6100cfa730SMark Brown struct wm831x *wm831x;
6200cfa730SMark Brown unsigned int data_irq;
6300cfa730SMark Brown unsigned int pd_irq;
6400cfa730SMark Brown bool pressure;
6500cfa730SMark Brown bool pen_down;
66f5346668SMark Brown struct work_struct pd_data_work;
6700cfa730SMark Brown };
6800cfa730SMark Brown
wm831x_pd_data_work(struct work_struct * work)69f5346668SMark Brown static void wm831x_pd_data_work(struct work_struct *work)
70f5346668SMark Brown {
71f5346668SMark Brown struct wm831x_ts *wm831x_ts =
72f5346668SMark Brown container_of(work, struct wm831x_ts, pd_data_work);
73f5346668SMark Brown
74f5346668SMark Brown if (wm831x_ts->pen_down) {
75f5346668SMark Brown enable_irq(wm831x_ts->data_irq);
76f5346668SMark Brown dev_dbg(wm831x_ts->wm831x->dev, "IRQ PD->DATA done\n");
77f5346668SMark Brown } else {
78f5346668SMark Brown enable_irq(wm831x_ts->pd_irq);
79f5346668SMark Brown dev_dbg(wm831x_ts->wm831x->dev, "IRQ DATA->PD done\n");
80f5346668SMark Brown }
81f5346668SMark Brown }
82f5346668SMark Brown
wm831x_ts_data_irq(int irq,void * irq_data)8300cfa730SMark Brown static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data)
8400cfa730SMark Brown {
8500cfa730SMark Brown struct wm831x_ts *wm831x_ts = irq_data;
8600cfa730SMark Brown struct wm831x *wm831x = wm831x_ts->wm831x;
8700cfa730SMark Brown static int data_types[] = { ABS_X, ABS_Y, ABS_PRESSURE };
8800cfa730SMark Brown u16 data[3];
89723d9284SMark Brown int count;
9000cfa730SMark Brown int i, ret;
9100cfa730SMark Brown
92723d9284SMark Brown if (wm831x_ts->pressure)
93723d9284SMark Brown count = 3;
94723d9284SMark Brown else
95723d9284SMark Brown count = 2;
96723d9284SMark Brown
9700cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
9800cfa730SMark Brown WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
9900cfa730SMark Brown
10000cfa730SMark Brown ret = wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count,
10100cfa730SMark Brown data);
10200cfa730SMark Brown if (ret != 0) {
10300cfa730SMark Brown dev_err(wm831x->dev, "Failed to read touch data: %d\n",
10400cfa730SMark Brown ret);
10500cfa730SMark Brown return IRQ_NONE;
10600cfa730SMark Brown }
10700cfa730SMark Brown
10800cfa730SMark Brown /*
10900cfa730SMark Brown * We get a pen down reading on every reading, report pen up if any
11000cfa730SMark Brown * individual reading does so.
11100cfa730SMark Brown */
11200cfa730SMark Brown wm831x_ts->pen_down = true;
11300cfa730SMark Brown for (i = 0; i < count; i++) {
11400cfa730SMark Brown if (!(data[i] & WM831X_TCH_PD)) {
11500cfa730SMark Brown wm831x_ts->pen_down = false;
11600cfa730SMark Brown continue;
11700cfa730SMark Brown }
11800cfa730SMark Brown input_report_abs(wm831x_ts->input_dev, data_types[i],
11900cfa730SMark Brown data[i] & WM831X_TCH_DATA_MASK);
12000cfa730SMark Brown }
12100cfa730SMark Brown
12200cfa730SMark Brown if (!wm831x_ts->pen_down) {
123f5346668SMark Brown /* Switch from data to pen down */
124f5346668SMark Brown dev_dbg(wm831x->dev, "IRQ DATA->PD\n");
125f5346668SMark Brown
12600cfa730SMark Brown disable_irq_nosync(wm831x_ts->data_irq);
12700cfa730SMark Brown
12800cfa730SMark Brown /* Don't need data any more */
12900cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
13000cfa730SMark Brown WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
13100cfa730SMark Brown WM831X_TCH_Z_ENA, 0);
13200cfa730SMark Brown
13300cfa730SMark Brown /* Flush any final samples that arrived while reading */
13400cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
13500cfa730SMark Brown WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
13600cfa730SMark Brown
13700cfa730SMark Brown wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count, data);
13800cfa730SMark Brown
13900cfa730SMark Brown if (wm831x_ts->pressure)
14000cfa730SMark Brown input_report_abs(wm831x_ts->input_dev,
14100cfa730SMark Brown ABS_PRESSURE, 0);
14200cfa730SMark Brown
14300cfa730SMark Brown input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 0);
144f5346668SMark Brown
145f5346668SMark Brown schedule_work(&wm831x_ts->pd_data_work);
146bf283707SMark Brown } else {
147bf283707SMark Brown input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 1);
14800cfa730SMark Brown }
14900cfa730SMark Brown
15000cfa730SMark Brown input_sync(wm831x_ts->input_dev);
15100cfa730SMark Brown
15200cfa730SMark Brown return IRQ_HANDLED;
15300cfa730SMark Brown }
15400cfa730SMark Brown
wm831x_ts_pen_down_irq(int irq,void * irq_data)15500cfa730SMark Brown static irqreturn_t wm831x_ts_pen_down_irq(int irq, void *irq_data)
15600cfa730SMark Brown {
15700cfa730SMark Brown struct wm831x_ts *wm831x_ts = irq_data;
15800cfa730SMark Brown struct wm831x *wm831x = wm831x_ts->wm831x;
159723d9284SMark Brown int ena = 0;
16000cfa730SMark Brown
161f5346668SMark Brown if (wm831x_ts->pen_down)
162f5346668SMark Brown return IRQ_HANDLED;
163f5346668SMark Brown
164f5346668SMark Brown disable_irq_nosync(wm831x_ts->pd_irq);
165f5346668SMark Brown
16600cfa730SMark Brown /* Start collecting data */
167723d9284SMark Brown if (wm831x_ts->pressure)
168723d9284SMark Brown ena |= WM831X_TCH_Z_ENA;
16900cfa730SMark Brown
17000cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
17100cfa730SMark Brown WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA,
17200cfa730SMark Brown WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | ena);
17300cfa730SMark Brown
17400cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
17500cfa730SMark Brown WM831X_TCHPD_EINT, WM831X_TCHPD_EINT);
17600cfa730SMark Brown
17700cfa730SMark Brown wm831x_ts->pen_down = true;
178f5346668SMark Brown
179f5346668SMark Brown /* Switch from pen down to data */
180f5346668SMark Brown dev_dbg(wm831x->dev, "IRQ PD->DATA\n");
181f5346668SMark Brown schedule_work(&wm831x_ts->pd_data_work);
18200cfa730SMark Brown
18300cfa730SMark Brown return IRQ_HANDLED;
18400cfa730SMark Brown }
18500cfa730SMark Brown
wm831x_ts_input_open(struct input_dev * idev)18600cfa730SMark Brown static int wm831x_ts_input_open(struct input_dev *idev)
18700cfa730SMark Brown {
18800cfa730SMark Brown struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
18900cfa730SMark Brown struct wm831x *wm831x = wm831x_ts->wm831x;
19000cfa730SMark Brown
19100cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
192e7cbb90aSMark Brown WM831X_TCH_ENA | WM831X_TCH_CVT_ENA |
193e7cbb90aSMark Brown WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
194e7cbb90aSMark Brown WM831X_TCH_Z_ENA, WM831X_TCH_ENA);
19500cfa730SMark Brown
19600cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
19700cfa730SMark Brown WM831X_TCH_CVT_ENA, WM831X_TCH_CVT_ENA);
19800cfa730SMark Brown
19900cfa730SMark Brown return 0;
20000cfa730SMark Brown }
20100cfa730SMark Brown
wm831x_ts_input_close(struct input_dev * idev)20200cfa730SMark Brown static void wm831x_ts_input_close(struct input_dev *idev)
20300cfa730SMark Brown {
20400cfa730SMark Brown struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
20500cfa730SMark Brown struct wm831x *wm831x = wm831x_ts->wm831x;
20600cfa730SMark Brown
207f5346668SMark Brown /* Shut the controller down, disabling all other functionality too */
20800cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
209f5346668SMark Brown WM831X_TCH_ENA | WM831X_TCH_X_ENA |
210f5346668SMark Brown WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA, 0);
21100cfa730SMark Brown
212f5346668SMark Brown /* Make sure any pending IRQs are done, the above will prevent
213f5346668SMark Brown * new ones firing.
214f5346668SMark Brown */
215f5346668SMark Brown synchronize_irq(wm831x_ts->data_irq);
216f5346668SMark Brown synchronize_irq(wm831x_ts->pd_irq);
217f5346668SMark Brown
218f5346668SMark Brown /* Make sure the IRQ completion work is quiesced */
21943829731STejun Heo flush_work(&wm831x_ts->pd_data_work);
220f5346668SMark Brown
221f5346668SMark Brown /* If we ended up with the pen down then make sure we revert back
222f5346668SMark Brown * to pen detection state for the next time we start up.
223f5346668SMark Brown */
224f5346668SMark Brown if (wm831x_ts->pen_down) {
22500cfa730SMark Brown disable_irq(wm831x_ts->data_irq);
226f5346668SMark Brown enable_irq(wm831x_ts->pd_irq);
227f5346668SMark Brown wm831x_ts->pen_down = false;
228f5346668SMark Brown }
22900cfa730SMark Brown }
23000cfa730SMark Brown
wm831x_ts_probe(struct platform_device * pdev)2315298cc4cSBill Pemberton static int wm831x_ts_probe(struct platform_device *pdev)
23200cfa730SMark Brown {
23300cfa730SMark Brown struct wm831x_ts *wm831x_ts;
23400cfa730SMark Brown struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
23500cfa730SMark Brown struct wm831x_pdata *core_pdata = dev_get_platdata(pdev->dev.parent);
236723d9284SMark Brown struct wm831x_touch_pdata *pdata = NULL;
23700cfa730SMark Brown struct input_dev *input_dev;
238acad9853SMark Brown int error, irqf;
23900cfa730SMark Brown
240723d9284SMark Brown if (core_pdata)
241723d9284SMark Brown pdata = core_pdata->touch;
242723d9284SMark Brown
243ef8dee5cSMark Brown wm831x_ts = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ts),
244ef8dee5cSMark Brown GFP_KERNEL);
245e7cd0aebSMark Brown input_dev = devm_input_allocate_device(&pdev->dev);
24600cfa730SMark Brown if (!wm831x_ts || !input_dev) {
24700cfa730SMark Brown error = -ENOMEM;
24800cfa730SMark Brown goto err_alloc;
24900cfa730SMark Brown }
25000cfa730SMark Brown
25100cfa730SMark Brown wm831x_ts->wm831x = wm831x;
25200cfa730SMark Brown wm831x_ts->input_dev = input_dev;
253f5346668SMark Brown INIT_WORK(&wm831x_ts->pd_data_work, wm831x_pd_data_work);
25400cfa730SMark Brown
25500cfa730SMark Brown /*
25600cfa730SMark Brown * If we have a direct IRQ use it, otherwise use the interrupt
25700cfa730SMark Brown * from the WM831x IRQ controller.
25800cfa730SMark Brown */
259cd99758bSMark Brown wm831x_ts->data_irq = wm831x_irq(wm831x,
260cd99758bSMark Brown platform_get_irq_byname(pdev,
261cd99758bSMark Brown "TCHDATA"));
26200cfa730SMark Brown if (pdata && pdata->data_irq)
26300cfa730SMark Brown wm831x_ts->data_irq = pdata->data_irq;
26400cfa730SMark Brown
265cd99758bSMark Brown wm831x_ts->pd_irq = wm831x_irq(wm831x,
266cd99758bSMark Brown platform_get_irq_byname(pdev, "TCHPD"));
26700cfa730SMark Brown if (pdata && pdata->pd_irq)
26800cfa730SMark Brown wm831x_ts->pd_irq = pdata->pd_irq;
26900cfa730SMark Brown
27023c483d2SMark Brown if (pdata)
27123c483d2SMark Brown wm831x_ts->pressure = pdata->pressure;
27223c483d2SMark Brown else
27323c483d2SMark Brown wm831x_ts->pressure = true;
27400cfa730SMark Brown
27500cfa730SMark Brown /* Five wire touchscreens can't report pressure */
27600cfa730SMark Brown if (pdata && pdata->fivewire) {
27700cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
27800cfa730SMark Brown WM831X_TCH_5WIRE, WM831X_TCH_5WIRE);
27900cfa730SMark Brown
28000cfa730SMark Brown /* Pressure measurements are not possible for five wire mode */
28100cfa730SMark Brown WARN_ON(pdata->pressure && pdata->fivewire);
28200cfa730SMark Brown wm831x_ts->pressure = false;
28300cfa730SMark Brown } else {
28400cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
28500cfa730SMark Brown WM831X_TCH_5WIRE, 0);
28600cfa730SMark Brown }
28700cfa730SMark Brown
28800cfa730SMark Brown if (pdata) {
28900cfa730SMark Brown switch (pdata->isel) {
29000cfa730SMark Brown default:
29100cfa730SMark Brown dev_err(&pdev->dev, "Unsupported ISEL setting: %d\n",
29200cfa730SMark Brown pdata->isel);
293df561f66SGustavo A. R. Silva fallthrough;
29400cfa730SMark Brown case 200:
29500cfa730SMark Brown case 0:
29600cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
29700cfa730SMark Brown WM831X_TCH_ISEL, 0);
29800cfa730SMark Brown break;
29900cfa730SMark Brown case 400:
30000cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
30100cfa730SMark Brown WM831X_TCH_ISEL, WM831X_TCH_ISEL);
30200cfa730SMark Brown break;
30300cfa730SMark Brown }
30400cfa730SMark Brown }
30500cfa730SMark Brown
30600cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
30700cfa730SMark Brown WM831X_TCH_PDONLY, 0);
30800cfa730SMark Brown
30900cfa730SMark Brown /* Default to 96 samples/sec */
31000cfa730SMark Brown wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
31100cfa730SMark Brown WM831X_TCH_RATE_MASK, 6);
31200cfa730SMark Brown
313acad9853SMark Brown if (pdata && pdata->data_irqf)
314acad9853SMark Brown irqf = pdata->data_irqf;
315acad9853SMark Brown else
316acad9853SMark Brown irqf = IRQF_TRIGGER_HIGH;
317acad9853SMark Brown
31800cfa730SMark Brown error = request_threaded_irq(wm831x_ts->data_irq,
31900cfa730SMark Brown NULL, wm831x_ts_data_irq,
320*bcd9730aSBarry Song irqf | IRQF_ONESHOT | IRQF_NO_AUTOEN,
32100cfa730SMark Brown "Touchscreen data", wm831x_ts);
32200cfa730SMark Brown if (error) {
32300cfa730SMark Brown dev_err(&pdev->dev, "Failed to request data IRQ %d: %d\n",
32400cfa730SMark Brown wm831x_ts->data_irq, error);
32500cfa730SMark Brown goto err_alloc;
32600cfa730SMark Brown }
32700cfa730SMark Brown
328acad9853SMark Brown if (pdata && pdata->pd_irqf)
329acad9853SMark Brown irqf = pdata->pd_irqf;
330acad9853SMark Brown else
331acad9853SMark Brown irqf = IRQF_TRIGGER_HIGH;
332acad9853SMark Brown
33300cfa730SMark Brown error = request_threaded_irq(wm831x_ts->pd_irq,
33400cfa730SMark Brown NULL, wm831x_ts_pen_down_irq,
335acad9853SMark Brown irqf | IRQF_ONESHOT,
33600cfa730SMark Brown "Touchscreen pen down", wm831x_ts);
33700cfa730SMark Brown if (error) {
33800cfa730SMark Brown dev_err(&pdev->dev, "Failed to request pen down IRQ %d: %d\n",
33900cfa730SMark Brown wm831x_ts->pd_irq, error);
34000cfa730SMark Brown goto err_data_irq;
34100cfa730SMark Brown }
34200cfa730SMark Brown
34300cfa730SMark Brown /* set up touch configuration */
34400cfa730SMark Brown input_dev->name = "WM831x touchscreen";
34500cfa730SMark Brown input_dev->phys = "wm831x";
34600cfa730SMark Brown input_dev->open = wm831x_ts_input_open;
34700cfa730SMark Brown input_dev->close = wm831x_ts_input_close;
34800cfa730SMark Brown
34900cfa730SMark Brown __set_bit(EV_ABS, input_dev->evbit);
35000cfa730SMark Brown __set_bit(EV_KEY, input_dev->evbit);
35100cfa730SMark Brown __set_bit(BTN_TOUCH, input_dev->keybit);
35200cfa730SMark Brown
35300cfa730SMark Brown input_set_abs_params(input_dev, ABS_X, 0, 4095, 5, 0);
35400cfa730SMark Brown input_set_abs_params(input_dev, ABS_Y, 0, 4095, 5, 0);
35500cfa730SMark Brown if (wm831x_ts->pressure)
35600cfa730SMark Brown input_set_abs_params(input_dev, ABS_PRESSURE, 0, 4095, 5, 0);
35700cfa730SMark Brown
35800cfa730SMark Brown input_set_drvdata(input_dev, wm831x_ts);
35900cfa730SMark Brown input_dev->dev.parent = &pdev->dev;
36000cfa730SMark Brown
36100cfa730SMark Brown error = input_register_device(input_dev);
36200cfa730SMark Brown if (error)
36300cfa730SMark Brown goto err_pd_irq;
36400cfa730SMark Brown
36500cfa730SMark Brown platform_set_drvdata(pdev, wm831x_ts);
36600cfa730SMark Brown return 0;
36700cfa730SMark Brown
36800cfa730SMark Brown err_pd_irq:
36900cfa730SMark Brown free_irq(wm831x_ts->pd_irq, wm831x_ts);
37000cfa730SMark Brown err_data_irq:
37100cfa730SMark Brown free_irq(wm831x_ts->data_irq, wm831x_ts);
37200cfa730SMark Brown err_alloc:
37300cfa730SMark Brown
37400cfa730SMark Brown return error;
37500cfa730SMark Brown }
37600cfa730SMark Brown
wm831x_ts_remove(struct platform_device * pdev)377e2619cf7SBill Pemberton static int wm831x_ts_remove(struct platform_device *pdev)
37800cfa730SMark Brown {
37900cfa730SMark Brown struct wm831x_ts *wm831x_ts = platform_get_drvdata(pdev);
38000cfa730SMark Brown
38100cfa730SMark Brown free_irq(wm831x_ts->pd_irq, wm831x_ts);
38200cfa730SMark Brown free_irq(wm831x_ts->data_irq, wm831x_ts);
38300cfa730SMark Brown
38400cfa730SMark Brown return 0;
38500cfa730SMark Brown }
38600cfa730SMark Brown
38700cfa730SMark Brown static struct platform_driver wm831x_ts_driver = {
38800cfa730SMark Brown .driver = {
38900cfa730SMark Brown .name = "wm831x-touch",
39000cfa730SMark Brown },
39100cfa730SMark Brown .probe = wm831x_ts_probe,
3921cb0aa88SBill Pemberton .remove = wm831x_ts_remove,
39300cfa730SMark Brown };
394cdcc96e2SJJ Ding module_platform_driver(wm831x_ts_driver);
39500cfa730SMark Brown
39600cfa730SMark Brown /* Module information */
39700cfa730SMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
39800cfa730SMark Brown MODULE_DESCRIPTION("WM831x PMIC touchscreen driver");
39900cfa730SMark Brown MODULE_LICENSE("GPL");
40000cfa730SMark Brown MODULE_ALIAS("platform:wm831x-touch");
401