da9034-ts.c 8.5 KB
Newer Older
E
Eric Miao 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
/*
 * Touchscreen driver for Dialog Semiconductor DA9034
 *
 * Copyright (C) 2006-2008 Marvell International Ltd.
 *	Fengwei Yin <fengwei.yin@marvell.com>
 *	Eric Miao <eric.miao@marvell.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/mfd/da903x.h>

#define DA9034_MANUAL_CTRL	0x50
#define DA9034_LDO_ADC_EN	(1 << 4)

#define DA9034_AUTO_CTRL1	0x51

#define DA9034_AUTO_CTRL2	0x52
#define DA9034_AUTO_TSI_EN	(1 << 3)
#define DA9034_PEN_DETECT	(1 << 4)

#define DA9034_TSI_CTRL1	0x53
#define DA9034_TSI_CTRL2	0x54
#define DA9034_TSI_X_MSB	0x6c
#define DA9034_TSI_Y_MSB	0x6d
#define DA9034_TSI_XY_LSB	0x6e

enum {
	STATE_IDLE,	/* wait for pendown */
	STATE_BUSY,	/* TSI busy sampling */
	STATE_STOP,	/* sample available */
	STATE_WAIT,	/* Wait to start next sample */
};

enum {
	EVENT_PEN_DOWN,
	EVENT_PEN_UP,
	EVENT_TSI_READY,
	EVENT_TIMEDOUT,
};

struct da9034_touch {
	struct device		*da9034_dev;
	struct input_dev	*input_dev;

	struct delayed_work	tsi_work;
	struct notifier_block	notifier;

	int	state;

	int	interval_ms;
	int	x_inverted;
	int	y_inverted;

	int	last_x;
	int	last_y;
};

static inline int is_pen_down(struct da9034_touch *touch)
{
	return da903x_query_status(touch->da9034_dev, DA9034_STATUS_PEN_DOWN);
}

static inline int detect_pen_down(struct da9034_touch *touch, int on)
{
	if (on)
		return da903x_set_bits(touch->da9034_dev,
				DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
	else
		return da903x_clr_bits(touch->da9034_dev,
				DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
}

static int read_tsi(struct da9034_touch *touch)
{
	uint8_t _x, _y, _v;
	int ret;

	ret = da903x_read(touch->da9034_dev, DA9034_TSI_X_MSB, &_x);
	if (ret)
		return ret;

	ret = da903x_read(touch->da9034_dev, DA9034_TSI_Y_MSB, &_y);
	if (ret)
		return ret;

	ret = da903x_read(touch->da9034_dev, DA9034_TSI_XY_LSB, &_v);
	if (ret)
		return ret;

	touch->last_x = ((_x << 2) & 0x3fc) | (_v & 0x3);
	touch->last_y = ((_y << 2) & 0x3fc) | ((_v & 0xc) >> 2);

	return 0;
}

static inline int start_tsi(struct da9034_touch *touch)
{
	return da903x_set_bits(touch->da9034_dev,
			DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
}

static inline int stop_tsi(struct da9034_touch *touch)
{
	return da903x_clr_bits(touch->da9034_dev,
			DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
}

static inline void report_pen_down(struct da9034_touch *touch)
{
	int x = touch->last_x;
	int y = touch->last_y;

	x &= 0xfff;
	if (touch->x_inverted)
		x = 1024 - x;
	y &= 0xfff;
	if (touch->y_inverted)
		y = 1024 - y;

	input_report_abs(touch->input_dev, ABS_X, x);
	input_report_abs(touch->input_dev, ABS_Y, y);
	input_report_key(touch->input_dev, BTN_TOUCH, 1);

	input_sync(touch->input_dev);
}

static inline void report_pen_up(struct da9034_touch *touch)
{
	input_report_key(touch->input_dev, BTN_TOUCH, 0);
	input_sync(touch->input_dev);
}

static void da9034_event_handler(struct da9034_touch *touch, int event)
{
	int err;

	switch (touch->state) {
	case STATE_IDLE:
		if (event != EVENT_PEN_DOWN)
			break;

		/* Enable auto measurement of the TSI, this will
		 * automatically disable pen down detection
		 */
		err = start_tsi(touch);
		if (err)
			goto err_reset;

		touch->state = STATE_BUSY;
		break;

	case STATE_BUSY:
		if (event != EVENT_TSI_READY)
			break;

		err = read_tsi(touch);
		if (err)
			goto err_reset;

		/* Disable auto measurement of the TSI, so that
		 * pen down status will be available
		 */
		err = stop_tsi(touch);
		if (err)
			goto err_reset;

		touch->state = STATE_STOP;
		break;

	case STATE_STOP:
		if (event == EVENT_PEN_DOWN) {
			report_pen_down(touch);
			schedule_delayed_work(&touch->tsi_work,
				msecs_to_jiffies(touch->interval_ms));
			touch->state = STATE_WAIT;
		}

		if (event == EVENT_PEN_UP) {
			report_pen_up(touch);
			touch->state = STATE_IDLE;
		}

		input_sync(touch->input_dev);
		break;

	case STATE_WAIT:
		if (event != EVENT_TIMEDOUT)
			break;

		if (is_pen_down(touch)) {
			start_tsi(touch);
			touch->state = STATE_BUSY;
		} else
			touch->state = STATE_IDLE;
		break;
	}
	return;

err_reset:
	touch->state = STATE_IDLE;
	stop_tsi(touch);
	detect_pen_down(touch, 1);
}

static void da9034_tsi_work(struct work_struct *work)
{
	struct da9034_touch *touch =
		container_of(work, struct da9034_touch, tsi_work.work);

	da9034_event_handler(touch, EVENT_TIMEDOUT);
}

static int da9034_touch_notifier(struct notifier_block *nb,
				 unsigned long event, void *data)
{
	struct da9034_touch *touch =
		container_of(nb, struct da9034_touch, notifier);

	if (event & DA9034_EVENT_PEN_DOWN) {
		if (is_pen_down(touch))
			da9034_event_handler(touch, EVENT_PEN_DOWN);
		else
			da9034_event_handler(touch, EVENT_PEN_UP);
	}

	if (event & DA9034_EVENT_TSI_READY)
		da9034_event_handler(touch, EVENT_TSI_READY);

	return 0;
}

static int da9034_touch_open(struct input_dev *dev)
{
	struct da9034_touch *touch = input_get_drvdata(dev);
	int ret;

	ret = da903x_register_notifier(touch->da9034_dev, &touch->notifier,
			DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
	if (ret)
		return -EBUSY;

	/* Enable ADC LDO */
	ret = da903x_set_bits(touch->da9034_dev,
			DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
	if (ret)
		return ret;

	/* TSI_DELAY: 3 slots, TSI_SKIP: 3 slots */
	ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL1, 0x1b);
	if (ret)
		return ret;

	ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL2, 0x00);
	if (ret)
		return ret;

	touch->state = STATE_IDLE;
	detect_pen_down(touch, 1);

	return 0;
}

static void da9034_touch_close(struct input_dev *dev)
{
	struct da9034_touch *touch = input_get_drvdata(dev);

	da903x_unregister_notifier(touch->da9034_dev, &touch->notifier,
			DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);

	cancel_delayed_work_sync(&touch->tsi_work);

	touch->state = STATE_IDLE;
	stop_tsi(touch);
	detect_pen_down(touch, 0);

	/* Disable ADC LDO */
	da903x_clr_bits(touch->da9034_dev,
			DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
}


static int __devinit da9034_touch_probe(struct platform_device *pdev)
{
	struct da9034_touch_pdata *pdata = pdev->dev.platform_data;
	struct da9034_touch *touch;
	struct input_dev *input_dev;
	int ret;

	touch = kzalloc(sizeof(struct da9034_touch), GFP_KERNEL);
	if (touch == NULL) {
		dev_err(&pdev->dev, "failed to allocate driver data\n");
		return -ENOMEM;
	}

	touch->da9034_dev = pdev->dev.parent;

	if (pdata) {
		touch->interval_ms	= pdata->interval_ms;
		touch->x_inverted	= pdata->x_inverted;
		touch->y_inverted	= pdata->y_inverted;
	} else
		/* fallback into default */
		touch->interval_ms	= 10;

	INIT_DELAYED_WORK(&touch->tsi_work, da9034_tsi_work);
	touch->notifier.notifier_call = da9034_touch_notifier;

	input_dev = input_allocate_device();
	if (!input_dev) {
		dev_err(&pdev->dev, "failed to allocate input device\n");
		ret = -ENOMEM;
		goto err_free_touch;
	}

	input_dev->name		= pdev->name;
	input_dev->open		= da9034_touch_open;
	input_dev->close	= da9034_touch_close;
	input_dev->dev.parent	= &pdev->dev;

	__set_bit(EV_ABS, input_dev->evbit);
	__set_bit(ABS_X, input_dev->absbit);
	__set_bit(ABS_Y, input_dev->absbit);
	input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
	input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);

	__set_bit(EV_KEY, input_dev->evbit);
	__set_bit(BTN_TOUCH, input_dev->keybit);

	touch->input_dev = input_dev;
	input_set_drvdata(input_dev, touch);

	ret = input_register_device(input_dev);
	if (ret)
		goto err_free_input;

	platform_set_drvdata(pdev, touch);
	return 0;

err_free_input:
	input_free_device(input_dev);
err_free_touch:
	kfree(touch);
	return ret;
}

static int __devexit da9034_touch_remove(struct platform_device *pdev)
{
	struct da9034_touch *touch = platform_get_drvdata(pdev);

	input_unregister_device(touch->input_dev);
	kfree(touch);

	return 0;
}

static struct platform_driver da9034_touch_driver = {
	.driver	= {
		.name	= "da9034-touch",
		.owner	= THIS_MODULE,
	},
	.probe		= da9034_touch_probe,
	.remove		= __devexit_p(da9034_touch_remove),
};

static int __init da9034_touch_init(void)
{
	return platform_driver_register(&da9034_touch_driver);
}
module_init(da9034_touch_init);

static void __exit da9034_touch_exit(void)
{
	platform_driver_unregister(&da9034_touch_driver);
}
module_exit(da9034_touch_exit);

MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9034");
MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:da9034-touch");