diff options
Diffstat (limited to 'drivers/input/mouse/navpoint.c')
| -rw-r--r-- | drivers/input/mouse/navpoint.c | 368 | 
1 files changed, 368 insertions, 0 deletions
diff --git a/drivers/input/mouse/navpoint.c b/drivers/input/mouse/navpoint.c new file mode 100644 index 00000000000..1ccc88af1f0 --- /dev/null +++ b/drivers/input/mouse/navpoint.c @@ -0,0 +1,368 @@ +/* + * Synaptics NavPoint (PXA27x SSP/SPI) driver. + * + * Copyright (C) 2012 Paul Parsons <lost.distance@yahoo.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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include <linux/input/navpoint.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/pxa2xx_ssp.h> +#include <linux/slab.h> + +/* + * Synaptics Modular Embedded Protocol: Module Packet Format. + * Module header byte 2:0 = Length (# bytes that follow) + * Module header byte 4:3 = Control + * Module header byte 7:5 = Module Address + */ +#define HEADER_LENGTH(byte)	((byte) & 0x07) +#define HEADER_CONTROL(byte)	(((byte) >> 3) & 0x03) +#define HEADER_ADDRESS(byte)	((byte) >> 5) + +struct navpoint { +	struct ssp_device	*ssp; +	struct input_dev	*input; +	struct device		*dev; +	int			gpio; +	int			index; +	u8			data[1 + HEADER_LENGTH(0xff)]; +}; + +/* + * Initialization values for SSCR0_x, SSCR1_x, SSSR_x. + */ +static const u32 sscr0 = 0 +	| SSCR0_TUM		/* TIM = 1; No TUR interrupts */ +	| SSCR0_RIM		/* RIM = 1; No ROR interrupts */ +	| SSCR0_SSE		/* SSE = 1; SSP enabled */ +	| SSCR0_Motorola	/* FRF = 0; Motorola SPI */ +	| SSCR0_DataSize(16)	/* DSS = 15; Data size = 16-bit */ +	; +static const u32 sscr1 = 0 +	| SSCR1_SCFR		/* SCFR = 1; SSPSCLK only during transfers */ +	| SSCR1_SCLKDIR		/* SCLKDIR = 1; Slave mode */ +	| SSCR1_SFRMDIR		/* SFRMDIR = 1; Slave mode */ +	| SSCR1_RWOT		/* RWOT = 1; Receive without transmit mode */ +	| SSCR1_RxTresh(1)	/* RFT = 0; Receive FIFO threshold = 1 */ +	| SSCR1_SPH		/* SPH = 1; SSPSCLK inactive 0.5 + 1 cycles */ +	| SSCR1_RIE		/* RIE = 1; Receive FIFO interrupt enabled */ +	; +static const u32 sssr = 0 +	| SSSR_BCE		/* BCE = 1; Clear BCE */ +	| SSSR_TUR		/* TUR = 1; Clear TUR */ +	| SSSR_EOC		/* EOC = 1; Clear EOC */ +	| SSSR_TINT		/* TINT = 1; Clear TINT */ +	| SSSR_PINT		/* PINT = 1; Clear PINT */ +	| SSSR_ROR		/* ROR = 1; Clear ROR */ +	; + +/* + * MEP Query $22: Touchpad Coordinate Range Query is not supported by + * the NavPoint module, so sampled values provide the default limits. + */ +#define NAVPOINT_X_MIN		1278 +#define NAVPOINT_X_MAX		5340 +#define NAVPOINT_Y_MIN		1572 +#define NAVPOINT_Y_MAX		4396 +#define NAVPOINT_PRESSURE_MIN	0 +#define NAVPOINT_PRESSURE_MAX	255 + +static void navpoint_packet(struct navpoint *navpoint) +{ +	int finger; +	int gesture; +	int x, y, z; + +	switch (navpoint->data[0]) { +	case 0xff:	/* Garbage (packet?) between reset and Hello packet */ +	case 0x00:	/* Module 0, NULL packet */ +		break; + +	case 0x0e:	/* Module 0, Absolute packet */ +		finger = (navpoint->data[1] & 0x01); +		gesture = (navpoint->data[1] & 0x02); +		x = ((navpoint->data[2] & 0x1f) << 8) | navpoint->data[3]; +		y = ((navpoint->data[4] & 0x1f) << 8) | navpoint->data[5]; +		z = navpoint->data[6]; +		input_report_key(navpoint->input, BTN_TOUCH, finger); +		input_report_abs(navpoint->input, ABS_X, x); +		input_report_abs(navpoint->input, ABS_Y, y); +		input_report_abs(navpoint->input, ABS_PRESSURE, z); +		input_report_key(navpoint->input, BTN_TOOL_FINGER, finger); +		input_report_key(navpoint->input, BTN_LEFT, gesture); +		input_sync(navpoint->input); +		break; + +	case 0x19:	/* Module 0, Hello packet */ +		if ((navpoint->data[1] & 0xf0) == 0x10) +			break; +		/* FALLTHROUGH */ +	default: +		dev_warn(navpoint->dev, +			 "spurious packet: data=0x%02x,0x%02x,...\n", +			 navpoint->data[0], navpoint->data[1]); +		break; +	} +} + +static irqreturn_t navpoint_irq(int irq, void *dev_id) +{ +	struct navpoint *navpoint = dev_id; +	struct ssp_device *ssp = navpoint->ssp; +	irqreturn_t ret = IRQ_NONE; +	u32 status; + +	status = pxa_ssp_read_reg(ssp, SSSR); +	if (status & sssr) { +		dev_warn(navpoint->dev, +			 "unexpected interrupt: status=0x%08x\n", status); +		pxa_ssp_write_reg(ssp, SSSR, (status & sssr)); +		ret = IRQ_HANDLED; +	} + +	while (status & SSSR_RNE) { +		u32 data; + +		data = pxa_ssp_read_reg(ssp, SSDR); +		navpoint->data[navpoint->index + 0] = (data >> 8); +		navpoint->data[navpoint->index + 1] = data; +		navpoint->index += 2; +		if (HEADER_LENGTH(navpoint->data[0]) < navpoint->index) { +			navpoint_packet(navpoint); +			navpoint->index = 0; +		} +		status = pxa_ssp_read_reg(ssp, SSSR); +		ret = IRQ_HANDLED; +	} + +	return ret; +} + +static void navpoint_up(struct navpoint *navpoint) +{ +	struct ssp_device *ssp = navpoint->ssp; +	int timeout; + +	clk_prepare_enable(ssp->clk); + +	pxa_ssp_write_reg(ssp, SSCR1, sscr1); +	pxa_ssp_write_reg(ssp, SSSR, sssr); +	pxa_ssp_write_reg(ssp, SSTO, 0); +	pxa_ssp_write_reg(ssp, SSCR0, sscr0);	/* SSCR0_SSE written last */ + +	/* Wait until SSP port is ready for slave clock operations */ +	for (timeout = 100; timeout != 0; --timeout) { +		if (!(pxa_ssp_read_reg(ssp, SSSR) & SSSR_CSS)) +			break; +		msleep(1); +	} + +	if (timeout == 0) +		dev_err(navpoint->dev, +			"timeout waiting for SSSR[CSS] to clear\n"); + +	if (gpio_is_valid(navpoint->gpio)) +		gpio_set_value(navpoint->gpio, 1); +} + +static void navpoint_down(struct navpoint *navpoint) +{ +	struct ssp_device *ssp = navpoint->ssp; + +	if (gpio_is_valid(navpoint->gpio)) +		gpio_set_value(navpoint->gpio, 0); + +	pxa_ssp_write_reg(ssp, SSCR0, 0); + +	clk_disable_unprepare(ssp->clk); +} + +static int navpoint_open(struct input_dev *input) +{ +	struct navpoint *navpoint = input_get_drvdata(input); + +	navpoint_up(navpoint); + +	return 0; +} + +static void navpoint_close(struct input_dev *input) +{ +	struct navpoint *navpoint = input_get_drvdata(input); + +	navpoint_down(navpoint); +} + +static int navpoint_probe(struct platform_device *pdev) +{ +	const struct navpoint_platform_data *pdata = +					dev_get_platdata(&pdev->dev); +	struct ssp_device *ssp; +	struct input_dev *input; +	struct navpoint *navpoint; +	int error; + +	if (!pdata) { +		dev_err(&pdev->dev, "no platform data\n"); +		return -EINVAL; +	} + +	if (gpio_is_valid(pdata->gpio)) { +		error = gpio_request_one(pdata->gpio, GPIOF_OUT_INIT_LOW, +					 "SYNAPTICS_ON"); +		if (error) +			return error; +	} + +	ssp = pxa_ssp_request(pdata->port, pdev->name); +	if (!ssp) { +		error = -ENODEV; +		goto err_free_gpio; +	} + +	/* HaRET does not disable devices before jumping into Linux */ +	if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) { +		pxa_ssp_write_reg(ssp, SSCR0, 0); +		dev_warn(&pdev->dev, "ssp%d already enabled\n", pdata->port); +	} + +	navpoint = kzalloc(sizeof(*navpoint), GFP_KERNEL); +	input = input_allocate_device(); +	if (!navpoint || !input) { +		error = -ENOMEM; +		goto err_free_mem; +	} + +	navpoint->ssp = ssp; +	navpoint->input = input; +	navpoint->dev = &pdev->dev; +	navpoint->gpio = pdata->gpio; + +	input->name = pdev->name; +	input->dev.parent = &pdev->dev; + +	__set_bit(EV_KEY, input->evbit); +	__set_bit(EV_ABS, input->evbit); +	__set_bit(BTN_LEFT, input->keybit); +	__set_bit(BTN_TOUCH, input->keybit); +	__set_bit(BTN_TOOL_FINGER, input->keybit); + +	input_set_abs_params(input, ABS_X, +			     NAVPOINT_X_MIN, NAVPOINT_X_MAX, 0, 0); +	input_set_abs_params(input, ABS_Y, +			     NAVPOINT_Y_MIN, NAVPOINT_Y_MAX, 0, 0); +	input_set_abs_params(input, ABS_PRESSURE, +			     NAVPOINT_PRESSURE_MIN, NAVPOINT_PRESSURE_MAX, +			     0, 0); + +	input->open = navpoint_open; +	input->close = navpoint_close; + +	input_set_drvdata(input, navpoint); + +	error = request_irq(ssp->irq, navpoint_irq, 0, pdev->name, navpoint); +	if (error) +		goto err_free_mem; + +	error = input_register_device(input); +	if (error) +		goto err_free_irq; + +	platform_set_drvdata(pdev, navpoint); +	dev_dbg(&pdev->dev, "ssp%d, irq %d\n", pdata->port, ssp->irq); + +	return 0; + +err_free_irq: +	free_irq(ssp->irq, navpoint); +err_free_mem: +	input_free_device(input); +	kfree(navpoint); +	pxa_ssp_free(ssp); +err_free_gpio: +	if (gpio_is_valid(pdata->gpio)) +		gpio_free(pdata->gpio); + +	return error; +} + +static int navpoint_remove(struct platform_device *pdev) +{ +	const struct navpoint_platform_data *pdata = +					dev_get_platdata(&pdev->dev); +	struct navpoint *navpoint = platform_get_drvdata(pdev); +	struct ssp_device *ssp = navpoint->ssp; + +	free_irq(ssp->irq, navpoint); + +	input_unregister_device(navpoint->input); +	kfree(navpoint); + +	pxa_ssp_free(ssp); + +	if (gpio_is_valid(pdata->gpio)) +		gpio_free(pdata->gpio); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int navpoint_suspend(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct navpoint *navpoint = platform_get_drvdata(pdev); +	struct input_dev *input = navpoint->input; + +	mutex_lock(&input->mutex); +	if (input->users) +		navpoint_down(navpoint); +	mutex_unlock(&input->mutex); + +	return 0; +} + +static int navpoint_resume(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct navpoint *navpoint = platform_get_drvdata(pdev); +	struct input_dev *input = navpoint->input; + +	mutex_lock(&input->mutex); +	if (input->users) +		navpoint_up(navpoint); +	mutex_unlock(&input->mutex); + +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(navpoint_pm_ops, navpoint_suspend, navpoint_resume); + +static struct platform_driver navpoint_driver = { +	.probe		= navpoint_probe, +	.remove		= navpoint_remove, +	.driver = { +		.name	= "navpoint", +		.owner	= THIS_MODULE, +		.pm	= &navpoint_pm_ops, +	}, +}; + +module_platform_driver(navpoint_driver); + +MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>"); +MODULE_DESCRIPTION("Synaptics NavPoint (PXA27x SSP/SPI) driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:navpoint");  | 
