Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 1 | /* drivers/input/touchscreen/msm_ts.c |
| 2 | * |
| 3 | * Copyright (C) 2008 Google, Inc. |
Duy Truong | 790f06d | 2013-02-13 16:38:12 -0800 | [diff] [blame^] | 4 | * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. |
Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 5 | * |
| 6 | * This software is licensed under the terms of the GNU General Public |
| 7 | * License version 2, as published by the Free Software Foundation, and |
| 8 | * may be copied, distributed, and modified under those terms. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * |
| 15 | * TODO: |
| 16 | * - Add a timer to simulate a pen_up in case there's a timeout. |
| 17 | */ |
| 18 | |
| 19 | #include <linux/delay.h> |
| 20 | #include <linux/device.h> |
| 21 | #include <linux/init.h> |
| 22 | #include <linux/input.h> |
| 23 | #include <linux/interrupt.h> |
| 24 | #include <linux/io.h> |
| 25 | #include <linux/module.h> |
| 26 | #include <linux/platform_device.h> |
| 27 | #include <linux/mfd/marimba-tsadc.h> |
| 28 | #include <linux/pm.h> |
| 29 | #include <linux/slab.h> |
| 30 | |
| 31 | #if defined(CONFIG_HAS_EARLYSUSPEND) |
| 32 | #include <linux/earlysuspend.h> |
| 33 | #endif |
| 34 | |
| 35 | #include <linux/input/msm_ts.h> |
| 36 | |
| 37 | #define TSSC_CTL 0x100 |
| 38 | #define TSSC_CTL_PENUP_IRQ (1 << 12) |
| 39 | #define TSSC_CTL_DATA_FLAG (1 << 11) |
| 40 | #define TSSC_CTL_DEBOUNCE_EN (1 << 6) |
| 41 | #define TSSC_CTL_EN_AVERAGE (1 << 5) |
| 42 | #define TSSC_CTL_MODE_MASTER (3 << 3) |
| 43 | #define TSSC_CTL_SW_RESET (1 << 2) |
| 44 | #define TSSC_CTL_ENABLE (1 << 0) |
| 45 | #define TSSC_OPN 0x104 |
| 46 | #define TSSC_OPN_NOOP 0x00 |
| 47 | #define TSSC_OPN_4WIRE_X 0x01 |
| 48 | #define TSSC_OPN_4WIRE_Y 0x02 |
| 49 | #define TSSC_OPN_4WIRE_Z1 0x03 |
| 50 | #define TSSC_OPN_4WIRE_Z2 0x04 |
| 51 | #define TSSC_SAMPLING_INT 0x108 |
| 52 | #define TSSC_STATUS 0x10c |
| 53 | #define TSSC_AVG_12 0x110 |
| 54 | #define TSSC_AVG_34 0x114 |
| 55 | #define TSSC_SAMPLE(op,samp) ((0x118 + ((op & 0x3) * 0x20)) + \ |
| 56 | ((samp & 0x7) * 0x4)) |
| 57 | #define TSSC_TEST_1 0x198 |
| 58 | #define TSSC_TEST_1_EN_GATE_DEBOUNCE (1 << 2) |
| 59 | #define TSSC_TEST_2 0x19c |
| 60 | |
| 61 | struct msm_ts { |
| 62 | struct msm_ts_platform_data *pdata; |
| 63 | struct input_dev *input_dev; |
| 64 | void __iomem *tssc_base; |
| 65 | uint32_t ts_down:1; |
| 66 | struct ts_virt_key *vkey_down; |
| 67 | struct marimba_tsadc_client *ts_client; |
| 68 | |
| 69 | unsigned int sample_irq; |
| 70 | unsigned int pen_up_irq; |
| 71 | |
| 72 | #if defined(CONFIG_HAS_EARLYSUSPEND) |
| 73 | struct early_suspend early_suspend; |
| 74 | #endif |
| 75 | struct device *dev; |
| 76 | }; |
| 77 | |
| 78 | static uint32_t msm_tsdebug; |
| 79 | module_param_named(tsdebug, msm_tsdebug, uint, 0664); |
| 80 | |
| 81 | #define tssc_readl(t, a) (readl_relaxed(((t)->tssc_base) + (a))) |
| 82 | #define tssc_writel(t, v, a) do {writel_relaxed(v, \ |
| 83 | ((t)->tssc_base) + (a)); } \ |
| 84 | while (0) |
| 85 | |
| 86 | static void setup_next_sample(struct msm_ts *ts) |
| 87 | { |
| 88 | uint32_t tmp; |
| 89 | |
| 90 | /* 1.2ms debounce time */ |
| 91 | tmp = ((2 << 7) | TSSC_CTL_DEBOUNCE_EN | TSSC_CTL_EN_AVERAGE | |
| 92 | TSSC_CTL_MODE_MASTER | TSSC_CTL_ENABLE); |
| 93 | tssc_writel(ts, tmp, TSSC_CTL); |
| 94 | /* barrier: Make sure the write completes before the next sample */ |
| 95 | mb(); |
| 96 | } |
| 97 | |
| 98 | static struct ts_virt_key *find_virt_key(struct msm_ts *ts, |
| 99 | struct msm_ts_virtual_keys *vkeys, |
| 100 | uint32_t val) |
| 101 | { |
| 102 | int i; |
| 103 | |
| 104 | if (!vkeys) |
| 105 | return NULL; |
| 106 | |
| 107 | for (i = 0; i < vkeys->num_keys; ++i) |
| 108 | if ((val >= vkeys->keys[i].min) && (val <= vkeys->keys[i].max)) |
| 109 | return &vkeys->keys[i]; |
| 110 | return NULL; |
| 111 | } |
| 112 | |
| 113 | |
| 114 | static irqreturn_t msm_ts_irq(int irq, void *dev_id) |
| 115 | { |
| 116 | struct msm_ts *ts = dev_id; |
| 117 | struct msm_ts_platform_data *pdata = ts->pdata; |
| 118 | |
| 119 | uint32_t tssc_avg12, tssc_avg34, tssc_status, tssc_ctl; |
| 120 | int x, y, z1, z2; |
| 121 | int was_down; |
| 122 | int down; |
| 123 | |
| 124 | tssc_ctl = tssc_readl(ts, TSSC_CTL); |
| 125 | tssc_status = tssc_readl(ts, TSSC_STATUS); |
| 126 | tssc_avg12 = tssc_readl(ts, TSSC_AVG_12); |
| 127 | tssc_avg34 = tssc_readl(ts, TSSC_AVG_34); |
| 128 | |
| 129 | setup_next_sample(ts); |
| 130 | |
| 131 | x = tssc_avg12 & 0xffff; |
| 132 | y = tssc_avg12 >> 16; |
| 133 | z1 = tssc_avg34 & 0xffff; |
| 134 | z2 = tssc_avg34 >> 16; |
| 135 | |
| 136 | /* invert the inputs if necessary */ |
| 137 | if (pdata->inv_x) x = pdata->inv_x - x; |
| 138 | if (pdata->inv_y) y = pdata->inv_y - y; |
| 139 | if (x < 0) x = 0; |
| 140 | if (y < 0) y = 0; |
| 141 | |
| 142 | down = !(tssc_ctl & TSSC_CTL_PENUP_IRQ); |
| 143 | was_down = ts->ts_down; |
| 144 | ts->ts_down = down; |
| 145 | |
| 146 | /* no valid data */ |
| 147 | if (down && !(tssc_ctl & TSSC_CTL_DATA_FLAG)) |
| 148 | return IRQ_HANDLED; |
| 149 | |
| 150 | if (msm_tsdebug & 2) |
| 151 | printk("%s: down=%d, x=%d, y=%d, z1=%d, z2=%d, status %x\n", |
| 152 | __func__, down, x, y, z1, z2, tssc_status); |
| 153 | |
| 154 | if (!was_down && down) { |
| 155 | struct ts_virt_key *vkey = NULL; |
| 156 | |
| 157 | if (pdata->vkeys_y && (y > pdata->virt_y_start)) |
| 158 | vkey = find_virt_key(ts, pdata->vkeys_y, x); |
| 159 | if (!vkey && ts->pdata->vkeys_x && (x > pdata->virt_x_start)) |
| 160 | vkey = find_virt_key(ts, pdata->vkeys_x, y); |
| 161 | |
| 162 | if (vkey) { |
| 163 | WARN_ON(ts->vkey_down != NULL); |
| 164 | if(msm_tsdebug) |
| 165 | printk("%s: virtual key down %d\n", __func__, |
| 166 | vkey->key); |
| 167 | ts->vkey_down = vkey; |
| 168 | input_report_key(ts->input_dev, vkey->key, 1); |
| 169 | input_sync(ts->input_dev); |
| 170 | return IRQ_HANDLED; |
| 171 | } |
| 172 | } else if (ts->vkey_down != NULL) { |
| 173 | if (!down) { |
| 174 | if(msm_tsdebug) |
| 175 | printk("%s: virtual key up %d\n", __func__, |
| 176 | ts->vkey_down->key); |
| 177 | input_report_key(ts->input_dev, ts->vkey_down->key, 0); |
| 178 | input_sync(ts->input_dev); |
| 179 | ts->vkey_down = NULL; |
| 180 | } |
| 181 | return IRQ_HANDLED; |
| 182 | } |
| 183 | |
| 184 | if (down) { |
| 185 | input_report_abs(ts->input_dev, ABS_X, x); |
| 186 | input_report_abs(ts->input_dev, ABS_Y, y); |
| 187 | input_report_abs(ts->input_dev, ABS_PRESSURE, z1); |
| 188 | } |
| 189 | input_report_key(ts->input_dev, BTN_TOUCH, down); |
| 190 | input_sync(ts->input_dev); |
| 191 | |
| 192 | return IRQ_HANDLED; |
| 193 | } |
| 194 | |
| 195 | static void dump_tssc_regs(struct msm_ts *ts) |
| 196 | { |
| 197 | #define __dump_tssc_reg(r) \ |
| 198 | do { printk(#r " %x\n", tssc_readl(ts, (r))); } while(0) |
| 199 | |
| 200 | __dump_tssc_reg(TSSC_CTL); |
| 201 | __dump_tssc_reg(TSSC_OPN); |
| 202 | __dump_tssc_reg(TSSC_SAMPLING_INT); |
| 203 | __dump_tssc_reg(TSSC_STATUS); |
| 204 | __dump_tssc_reg(TSSC_AVG_12); |
| 205 | __dump_tssc_reg(TSSC_AVG_34); |
| 206 | __dump_tssc_reg(TSSC_TEST_1); |
| 207 | #undef __dump_tssc_reg |
| 208 | } |
| 209 | |
Stephen Boyd | 93d0d19 | 2012-04-25 11:50:07 -0700 | [diff] [blame] | 210 | static int msm_ts_hw_init(struct msm_ts *ts) |
Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 211 | { |
| 212 | uint32_t tmp; |
| 213 | |
| 214 | /* Enable the register clock to tssc so we can configure it. */ |
| 215 | tssc_writel(ts, TSSC_CTL_ENABLE, TSSC_CTL); |
| 216 | /* Enable software reset*/ |
| 217 | tssc_writel(ts, TSSC_CTL_SW_RESET, TSSC_CTL); |
| 218 | |
| 219 | /* op1 - measure X, 1 sample, 12bit resolution */ |
| 220 | tmp = (TSSC_OPN_4WIRE_X << 16) | (2 << 8) | (2 << 0); |
| 221 | /* op2 - measure Y, 1 sample, 12bit resolution */ |
| 222 | tmp |= (TSSC_OPN_4WIRE_Y << 20) | (2 << 10) | (2 << 2); |
| 223 | /* op3 - measure Z1, 1 sample, 8bit resolution */ |
| 224 | tmp |= (TSSC_OPN_4WIRE_Z1 << 24) | (2 << 12) | (0 << 4); |
| 225 | |
| 226 | /* XXX: we don't actually need to measure Z2 (thus 0 samples) when |
| 227 | * doing voltage-driven measurement */ |
| 228 | /* op4 - measure Z2, 0 samples, 8bit resolution */ |
| 229 | tmp |= (TSSC_OPN_4WIRE_Z2 << 28) | (0 << 14) | (0 << 6); |
| 230 | tssc_writel(ts, tmp, TSSC_OPN); |
| 231 | |
| 232 | /* 16ms sampling interval */ |
| 233 | tssc_writel(ts, 16, TSSC_SAMPLING_INT); |
| 234 | /* Enable gating logic to fix the timing delays caused because of |
| 235 | * enabling debounce logic */ |
| 236 | tssc_writel(ts, TSSC_TEST_1_EN_GATE_DEBOUNCE, TSSC_TEST_1); |
| 237 | |
| 238 | setup_next_sample(ts); |
| 239 | |
| 240 | return 0; |
| 241 | } |
| 242 | |
| 243 | static void msm_ts_enable(struct msm_ts *ts, bool enable) |
| 244 | { |
| 245 | uint32_t val; |
| 246 | |
| 247 | if (enable == true) |
| 248 | msm_ts_hw_init(ts); |
| 249 | else { |
| 250 | val = tssc_readl(ts, TSSC_CTL); |
| 251 | val &= ~TSSC_CTL_ENABLE; |
| 252 | tssc_writel(ts, val, TSSC_CTL); |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | #ifdef CONFIG_PM |
| 257 | static int |
| 258 | msm_ts_suspend(struct device *dev) |
| 259 | { |
| 260 | struct msm_ts *ts = dev_get_drvdata(dev); |
| 261 | |
| 262 | if (device_may_wakeup(dev) && |
| 263 | device_may_wakeup(dev->parent)) |
| 264 | enable_irq_wake(ts->sample_irq); |
| 265 | else { |
| 266 | disable_irq(ts->sample_irq); |
| 267 | disable_irq(ts->pen_up_irq); |
| 268 | msm_ts_enable(ts, false); |
| 269 | } |
| 270 | |
| 271 | return 0; |
| 272 | } |
| 273 | |
| 274 | static int |
| 275 | msm_ts_resume(struct device *dev) |
| 276 | { |
| 277 | struct msm_ts *ts = dev_get_drvdata(dev); |
| 278 | |
| 279 | if (device_may_wakeup(dev) && |
| 280 | device_may_wakeup(dev->parent)) |
| 281 | disable_irq_wake(ts->sample_irq); |
| 282 | else { |
| 283 | msm_ts_enable(ts, true); |
| 284 | enable_irq(ts->sample_irq); |
| 285 | enable_irq(ts->pen_up_irq); |
| 286 | } |
| 287 | |
| 288 | return 0; |
| 289 | } |
| 290 | |
| 291 | static struct dev_pm_ops msm_touchscreen_pm_ops = { |
| 292 | #ifndef CONFIG_HAS_EARLYSUSPEND |
| 293 | .suspend = msm_ts_suspend, |
| 294 | .resume = msm_ts_resume, |
| 295 | #endif |
| 296 | }; |
| 297 | #endif |
| 298 | |
| 299 | #ifdef CONFIG_HAS_EARLYSUSPEND |
| 300 | static void msm_ts_early_suspend(struct early_suspend *h) |
| 301 | { |
| 302 | struct msm_ts *ts = container_of(h, struct msm_ts, early_suspend); |
| 303 | |
| 304 | msm_ts_suspend(ts->dev); |
| 305 | } |
| 306 | |
| 307 | static void msm_ts_late_resume(struct early_suspend *h) |
| 308 | { |
| 309 | struct msm_ts *ts = container_of(h, struct msm_ts, early_suspend); |
| 310 | |
| 311 | msm_ts_resume(ts->dev); |
| 312 | } |
| 313 | #endif |
| 314 | |
| 315 | |
| 316 | static int __devinit msm_ts_probe(struct platform_device *pdev) |
| 317 | { |
| 318 | struct msm_ts_platform_data *pdata = pdev->dev.platform_data; |
| 319 | struct msm_ts *ts; |
| 320 | struct resource *tssc_res; |
| 321 | struct resource *irq1_res; |
| 322 | struct resource *irq2_res; |
| 323 | int err = 0; |
| 324 | int i; |
| 325 | struct marimba_tsadc_client *ts_client; |
| 326 | |
| 327 | printk("%s\n", __func__); |
| 328 | |
| 329 | tssc_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tssc"); |
| 330 | irq1_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "tssc1"); |
| 331 | irq2_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "tssc2"); |
| 332 | |
| 333 | if (!tssc_res || !irq1_res || !irq2_res) { |
| 334 | pr_err("%s: required resources not defined\n", __func__); |
| 335 | return -ENODEV; |
| 336 | } |
| 337 | |
| 338 | if (pdata == NULL) { |
| 339 | pr_err("%s: missing platform_data\n", __func__); |
| 340 | return -ENODEV; |
| 341 | } |
| 342 | |
| 343 | ts = kzalloc(sizeof(struct msm_ts), GFP_KERNEL); |
| 344 | if (ts == NULL) { |
| 345 | pr_err("%s: No memory for struct msm_ts\n", __func__); |
| 346 | return -ENOMEM; |
| 347 | } |
| 348 | ts->pdata = pdata; |
| 349 | ts->dev = &pdev->dev; |
| 350 | |
| 351 | ts->sample_irq = irq1_res->start; |
| 352 | ts->pen_up_irq = irq2_res->start; |
| 353 | |
| 354 | ts->tssc_base = ioremap(tssc_res->start, resource_size(tssc_res)); |
| 355 | if (ts->tssc_base == NULL) { |
| 356 | pr_err("%s: Can't ioremap region (0x%08x - 0x%08x)\n", __func__, |
| 357 | (uint32_t)tssc_res->start, (uint32_t)tssc_res->end); |
| 358 | err = -ENOMEM; |
| 359 | goto err_ioremap_tssc; |
| 360 | } |
| 361 | |
| 362 | ts_client = marimba_tsadc_register(pdev, 1); |
| 363 | if (IS_ERR(ts_client)) { |
| 364 | pr_err("%s: Unable to register with TSADC\n", __func__); |
| 365 | err = -ENOMEM; |
| 366 | goto err_tsadc_register; |
| 367 | } |
| 368 | ts->ts_client = ts_client; |
| 369 | |
| 370 | err = marimba_tsadc_start(ts_client); |
| 371 | if (err) { |
| 372 | pr_err("%s: Unable to start TSADC\n", __func__); |
| 373 | err = -EINVAL; |
| 374 | goto err_start_tsadc; |
| 375 | } |
| 376 | |
| 377 | ts->input_dev = input_allocate_device(); |
| 378 | if (ts->input_dev == NULL) { |
| 379 | pr_err("failed to allocate touchscreen input device\n"); |
| 380 | err = -ENOMEM; |
| 381 | goto err_alloc_input_dev; |
| 382 | } |
| 383 | ts->input_dev->name = "msm-touchscreen"; |
| 384 | ts->input_dev->dev.parent = &pdev->dev; |
| 385 | |
| 386 | input_set_drvdata(ts->input_dev, ts); |
| 387 | |
| 388 | input_set_capability(ts->input_dev, EV_KEY, BTN_TOUCH); |
| 389 | set_bit(EV_ABS, ts->input_dev->evbit); |
Anirudh Ghayal | 3aaf03e | 2012-07-26 13:25:17 +0530 | [diff] [blame] | 390 | set_bit(INPUT_PROP_DIRECT, ts->input_dev->propbit); |
Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 391 | |
| 392 | input_set_abs_params(ts->input_dev, ABS_X, pdata->min_x, pdata->max_x, |
| 393 | 0, 0); |
| 394 | input_set_abs_params(ts->input_dev, ABS_Y, pdata->min_y, pdata->max_y, |
| 395 | 0, 0); |
| 396 | input_set_abs_params(ts->input_dev, ABS_PRESSURE, pdata->min_press, |
| 397 | pdata->max_press, 0, 0); |
| 398 | |
| 399 | for (i = 0; pdata->vkeys_x && (i < pdata->vkeys_x->num_keys); ++i) |
| 400 | input_set_capability(ts->input_dev, EV_KEY, |
| 401 | pdata->vkeys_x->keys[i].key); |
| 402 | for (i = 0; pdata->vkeys_y && (i < pdata->vkeys_y->num_keys); ++i) |
| 403 | input_set_capability(ts->input_dev, EV_KEY, |
| 404 | pdata->vkeys_y->keys[i].key); |
| 405 | |
| 406 | err = input_register_device(ts->input_dev); |
| 407 | if (err != 0) { |
| 408 | pr_err("%s: failed to register input device\n", __func__); |
| 409 | goto err_input_dev_reg; |
| 410 | } |
| 411 | |
| 412 | msm_ts_hw_init(ts); |
| 413 | |
| 414 | err = request_irq(ts->sample_irq, msm_ts_irq, |
| 415 | (irq1_res->flags & ~IORESOURCE_IRQ) | IRQF_DISABLED, |
| 416 | "msm_touchscreen", ts); |
| 417 | if (err != 0) { |
| 418 | pr_err("%s: Cannot register irq1 (%d)\n", __func__, err); |
| 419 | goto err_request_irq1; |
| 420 | } |
| 421 | |
| 422 | err = request_irq(ts->pen_up_irq, msm_ts_irq, |
| 423 | (irq2_res->flags & ~IORESOURCE_IRQ) | IRQF_DISABLED, |
| 424 | "msm_touchscreen", ts); |
| 425 | if (err != 0) { |
| 426 | pr_err("%s: Cannot register irq2 (%d)\n", __func__, err); |
| 427 | goto err_request_irq2; |
| 428 | } |
| 429 | |
| 430 | platform_set_drvdata(pdev, ts); |
| 431 | |
| 432 | #ifdef CONFIG_HAS_EARLYSUSPEND |
| 433 | ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + |
| 434 | TSSC_SUSPEND_LEVEL; |
| 435 | ts->early_suspend.suspend = msm_ts_early_suspend; |
| 436 | ts->early_suspend.resume = msm_ts_late_resume; |
| 437 | register_early_suspend(&ts->early_suspend); |
| 438 | #endif |
| 439 | |
| 440 | device_init_wakeup(&pdev->dev, pdata->can_wakeup); |
| 441 | pr_info("%s: tssc_base=%p irq1=%d irq2=%d\n", __func__, |
| 442 | ts->tssc_base, (int)ts->sample_irq, (int)ts->pen_up_irq); |
| 443 | dump_tssc_regs(ts); |
| 444 | return 0; |
| 445 | |
| 446 | err_request_irq2: |
| 447 | free_irq(ts->sample_irq, ts); |
| 448 | |
| 449 | err_request_irq1: |
| 450 | /* disable the tssc */ |
| 451 | tssc_writel(ts, TSSC_CTL_ENABLE, TSSC_CTL); |
| 452 | |
| 453 | err_input_dev_reg: |
| 454 | input_set_drvdata(ts->input_dev, NULL); |
| 455 | input_free_device(ts->input_dev); |
| 456 | |
| 457 | err_alloc_input_dev: |
| 458 | err_start_tsadc: |
| 459 | marimba_tsadc_unregister(ts->ts_client); |
| 460 | |
| 461 | err_tsadc_register: |
| 462 | iounmap(ts->tssc_base); |
| 463 | |
| 464 | err_ioremap_tssc: |
| 465 | kfree(ts); |
| 466 | return err; |
| 467 | } |
| 468 | |
| 469 | static int __devexit msm_ts_remove(struct platform_device *pdev) |
| 470 | { |
| 471 | struct msm_ts *ts = platform_get_drvdata(pdev); |
| 472 | |
| 473 | device_init_wakeup(&pdev->dev, 0); |
| 474 | marimba_tsadc_unregister(ts->ts_client); |
| 475 | free_irq(ts->sample_irq, ts); |
| 476 | free_irq(ts->pen_up_irq, ts); |
| 477 | input_unregister_device(ts->input_dev); |
| 478 | iounmap(ts->tssc_base); |
| 479 | #ifdef CONFIG_HAS_EARLYSUSPEND |
| 480 | unregister_early_suspend(&ts->early_suspend); |
| 481 | #endif |
| 482 | platform_set_drvdata(pdev, NULL); |
| 483 | kfree(ts); |
| 484 | |
| 485 | return 0; |
| 486 | } |
| 487 | |
| 488 | static struct platform_driver msm_touchscreen_driver = { |
| 489 | .driver = { |
| 490 | .name = "msm_touchscreen", |
| 491 | .owner = THIS_MODULE, |
| 492 | #ifdef CONFIG_PM |
| 493 | .pm = &msm_touchscreen_pm_ops, |
| 494 | #endif |
| 495 | }, |
| 496 | .probe = msm_ts_probe, |
| 497 | .remove = __devexit_p(msm_ts_remove), |
| 498 | }; |
| 499 | |
| 500 | static int __init msm_ts_init(void) |
| 501 | { |
| 502 | return platform_driver_register(&msm_touchscreen_driver); |
| 503 | } |
| 504 | |
| 505 | static void __exit msm_ts_exit(void) |
| 506 | { |
| 507 | platform_driver_unregister(&msm_touchscreen_driver); |
| 508 | } |
| 509 | |
| 510 | module_init(msm_ts_init); |
| 511 | module_exit(msm_ts_exit); |
| 512 | MODULE_DESCRIPTION("Qualcomm MSM/QSD Touchscreen controller driver"); |
| 513 | MODULE_LICENSE("GPL"); |
| 514 | MODULE_ALIAS("platform:msm_touchscreen"); |