diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-05-01 13:20:04 -0700 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-05-01 13:20:04 -0700 | 
| commit | 251df49db3327c64bf917bfdba94491fde2b4ee0 (patch) | |
| tree | 71eef72e1c393057f7b14cc4d8da5e48c7728336 | |
| parent | 8a72f3820c4d14b27ad5336aed00063a7a7f1bef (diff) | |
| parent | bf61c8840efe60fd8f91446860b63338fb424158 (diff) | |
| download | olio-linux-3.10-251df49db3327c64bf917bfdba94491fde2b4ee0.tar.xz olio-linux-3.10-251df49db3327c64bf917bfdba94491fde2b4ee0.zip  | |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
Pull input updates from Dmitry Torokhov:
 "Assorted fixes and cleanups to the existing drivers plus a new driver
  for IMS Passenger Control Unit device they use for ther in-flight
  entertainment system."
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (44 commits)
  Input: trackpoint - Optimize trackpoint init to use power-on reset
  Input: apbps2 - convert to devm_ioremap_resource()
  Input: ALPS - use %ph to print buffers
  ARM - shmobile: Armadillo800EVA: Move st1232 reset pin handling
  Input: st1232 - add reset pin handling
  Input: st1232 - convert to devm_* infrastructure
  Input: MT - handle semi-mt devices in core
  Input: adxl34x - use spi_get_drvdata()
  Input: ad7877 - use spi_get_drvdata() and spi_set_drvdata()
  Input: ads7846 - use spi_get_drvdata() and spi_set_drvdata()
  Input: ims-pcu - fix a memory leak on error
  Input: sysrq - supplement reset sequence with timeout functionality
  Input: tegra-kbc - support for defining row/columns based on SoC
  Input: imx_keypad - switch to using managed resources
  Input: arc_ps2 - add support for device tree
  Input: mma8450 - fix signed 12bits to 32bits conversion
  Input: eeti_ts - remove redundant null check
  Input: edt-ft5x06 - remove redundant null check before kfree
  Input: ad714x - add CONFIG_PM_SLEEP to suspend/resume functions
  Input: adxl34x - add CONFIG_PM_SLEEP to suspend/resume functions
  ...
49 files changed, 2911 insertions, 438 deletions
diff --git a/Documentation/devicetree/bindings/input/ps2keyb-mouse-apbps2.txt b/Documentation/devicetree/bindings/input/ps2keyb-mouse-apbps2.txt new file mode 100644 index 00000000000..3029c5694cf --- /dev/null +++ b/Documentation/devicetree/bindings/input/ps2keyb-mouse-apbps2.txt @@ -0,0 +1,16 @@ +Aeroflex Gaisler APBPS2 PS/2 Core, supporting Keyboard or Mouse. + +The APBPS2 PS/2 core is available in the GRLIB VHDL IP core library. + +Note: In the ordinary environment for the APBPS2 core, a LEON SPARC system, +these properties are built from information in the AMBA plug&play and from +bootloader settings. + +Required properties: + +- name : Should be "GAISLER_APBPS2" or "01_060" +- reg : Address and length of the register set for the device +- interrupts : Interrupt numbers for this device + +For further information look in the documentation for the GLIB IP core library: +http://www.gaisler.com/products/grlib/grip.pdf diff --git a/Documentation/devicetree/bindings/input/touchscreen/auo_pixcir_ts.txt b/Documentation/devicetree/bindings/input/touchscreen/auo_pixcir_ts.txt new file mode 100644 index 00000000000..f40f21c642b --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/auo_pixcir_ts.txt @@ -0,0 +1,30 @@ +* AUO in-cell touchscreen controller using Pixcir sensors + +Required properties: +- compatible: must be "auo,auo_pixcir_ts" +- reg: I2C address of the chip +- interrupts: interrupt to which the chip is connected +- gpios: gpios the chip is connected to +  first one is the interrupt gpio and second one the reset gpio +- x-size: horizontal resolution of touchscreen +- y-size: vertical resolution of touchscreen + +Example: + +	i2c@00000000 { +		/* ... */ + +		auo_pixcir_ts@5c { +			compatible = "auo,auo_pixcir_ts"; +			reg = <0x5c>; +			interrupts = <2 0>; + +			gpios = <&gpf 2 0 2>, /* INT */ +				<&gpf 5 1 0>; /* RST */ + +			x-size = <800>; +			y-size = <600>; +		}; + +		/* ... */ +	}; diff --git a/Documentation/devicetree/bindings/input/touchscreen/sitronix-st1232.txt b/Documentation/devicetree/bindings/input/touchscreen/sitronix-st1232.txt new file mode 100644 index 00000000000..64ad48b824a --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/sitronix-st1232.txt @@ -0,0 +1,24 @@ +* Sitronix st1232 touchscreen controller + +Required properties: +- compatible: must be "sitronix,st1232" +- reg: I2C address of the chip +- interrupts: interrupt to which the chip is connected + +Optional properties: +- gpios: a phandle to the reset GPIO + +Example: + +	i2c@00000000 { +		/* ... */ + +		touchscreen@55 { +			compatible = "sitronix,st1232"; +			reg = <0x55>; +			interrupts = <2 0>; +			gpios = <&gpio1 166 0>; +		}; + +		/* ... */ +	}; diff --git a/Documentation/devicetree/bindings/serio/snps-arc_ps2.txt b/Documentation/devicetree/bindings/serio/snps-arc_ps2.txt new file mode 100644 index 00000000000..38c2f21e804 --- /dev/null +++ b/Documentation/devicetree/bindings/serio/snps-arc_ps2.txt @@ -0,0 +1,16 @@ +* ARC PS/2 driver: PS/2 block used in some ARC FPGA's & nSIM OSCI model + +Required properties: +- compatible		: "snps,arc_ps2" +- reg			: offset and length (always 0x14) of registers +- interrupts		: interrupt +- interrupt-names	: name of interrupt, must be "arc_ps2_irq" + +Example: + +serio@c9000400 { +	compatible = "snps,arc_ps2"; +	reg = <0xc9000400 0x14>; +	interrupts = <13>; +	interrupt-names = "arc_ps2_irq"; +} diff --git a/arch/arm/mach-shmobile/board-armadillo800eva.c b/arch/arm/mach-shmobile/board-armadillo800eva.c index ff8b7ba9b93..881e5c0e41d 100644 --- a/arch/arm/mach-shmobile/board-armadillo800eva.c +++ b/arch/arm/mach-shmobile/board-armadillo800eva.c @@ -24,6 +24,7 @@  #include <linux/err.h>  #include <linux/kernel.h>  #include <linux/input.h> +#include <linux/platform_data/st1232_pdata.h>  #include <linux/irq.h>  #include <linux/platform_device.h>  #include <linux/gpio.h> @@ -882,10 +883,15 @@ static struct platform_device i2c_gpio_device = {  };  /* I2C */ +static struct st1232_pdata st1232_i2c0_pdata = { +	.reset_gpio = 166, +}; +  static struct i2c_board_info i2c0_devices[] = {  	{  		I2C_BOARD_INFO("st1232-ts", 0x55),  		.irq = evt2irq(0x0340), +		.platform_data = &st1232_i2c0_pdata,  	},  	{  		I2C_BOARD_INFO("wm8978", 0x1a), @@ -1009,7 +1015,6 @@ static void __init eva_init(void)  	/* Touchscreen */  	gpio_request(GPIO_FN_IRQ10,	NULL); /* TP_INT */ -	gpio_request_one(GPIO_PORT166, GPIOF_OUT_INIT_HIGH, NULL); /* TP_RST_B */  	/* GETHER */  	gpio_request(GPIO_FN_ET_CRS,		NULL); diff --git a/drivers/base/devres.c b/drivers/base/devres.c index 66839066476..507379e7b76 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -671,6 +671,80 @@ int devres_release_group(struct device *dev, void *id)  EXPORT_SYMBOL_GPL(devres_release_group);  /* + * Custom devres actions allow inserting a simple function call + * into the teadown sequence. + */ + +struct action_devres { +	void *data; +	void (*action)(void *); +}; + +static int devm_action_match(struct device *dev, void *res, void *p) +{ +	struct action_devres *devres = res; +	struct action_devres *target = p; + +	return devres->action == target->action && +	       devres->data == target->data; +} + +static void devm_action_release(struct device *dev, void *res) +{ +	struct action_devres *devres = res; + +	devres->action(devres->data); +} + +/** + * devm_add_action() - add a custom action to list of managed resources + * @dev: Device that owns the action + * @action: Function that should be called + * @data: Pointer to data passed to @action implementation + * + * This adds a custom action to the list of managed resources so that + * it gets executed as part of standard resource unwinding. + */ +int devm_add_action(struct device *dev, void (*action)(void *), void *data) +{ +	struct action_devres *devres; + +	devres = devres_alloc(devm_action_release, +			      sizeof(struct action_devres), GFP_KERNEL); +	if (!devres) +		return -ENOMEM; + +	devres->data = data; +	devres->action = action; + +	devres_add(dev, devres); +	return 0; +} +EXPORT_SYMBOL_GPL(devm_add_action); + +/** + * devm_remove_action() - removes previously added custom action + * @dev: Device that owns the action + * @action: Function implementing the action + * @data: Pointer to data passed to @action implementation + * + * Removes instance of @action previously added by devm_add_action(). + * Both action and data should match one of the existing entries. + */ +void devm_remove_action(struct device *dev, void (*action)(void *), void *data) +{ +	struct action_devres devres = { +		.data = data, +		.action = action, +	}; + +	WARN_ON(devres_destroy(dev, devm_action_release, devm_action_match, +			       &devres)); + +} +EXPORT_SYMBOL_GPL(devm_remove_action); + +/*   * Managed kzalloc/kfree   */  static void devm_kzalloc_release(struct device *dev, void *res) diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c index 71db1930573..d398f1321f1 100644 --- a/drivers/input/input-mt.c +++ b/drivers/input/input-mt.c @@ -79,6 +79,8 @@ int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots,  	}  	if (flags & INPUT_MT_DIRECT)  		__set_bit(INPUT_PROP_DIRECT, dev->propbit); +	if (flags & INPUT_MT_SEMI_MT) +		__set_bit(INPUT_PROP_SEMI_MT, dev->propbit);  	if (flags & INPUT_MT_TRACK) {  		unsigned int n2 = num_slots * num_slots;  		mt->red = kcalloc(n2, sizeof(*mt->red), GFP_KERNEL); @@ -246,6 +248,7 @@ void input_mt_sync_frame(struct input_dev *dev)  {  	struct input_mt *mt = dev->mt;  	struct input_mt_slot *s; +	bool use_count = false;  	if (!mt)  		return; @@ -259,7 +262,10 @@ void input_mt_sync_frame(struct input_dev *dev)  		}  	} -	input_mt_report_pointer_emulation(dev, (mt->flags & INPUT_MT_POINTER)); +	if ((mt->flags & INPUT_MT_POINTER) && !(mt->flags & INPUT_MT_SEMI_MT)) +		use_count = true; + +	input_mt_report_pointer_emulation(dev, use_count);  	mt->frame++;  } diff --git a/drivers/input/keyboard/amikbd.c b/drivers/input/keyboard/amikbd.c index 79172af164f..ba0b36f7dae 100644 --- a/drivers/input/keyboard/amikbd.c +++ b/drivers/input/keyboard/amikbd.c @@ -260,18 +260,6 @@ static struct platform_driver amikbd_driver = {  	},  }; -static int __init amikbd_init(void) -{ -	return platform_driver_probe(&amikbd_driver, amikbd_probe); -} - -module_init(amikbd_init); - -static void __exit amikbd_exit(void) -{ -	platform_driver_unregister(&amikbd_driver); -} - -module_exit(amikbd_exit); +module_platform_driver_probe(amikbd_driver, amikbd_probe);  MODULE_ALIAS("platform:amiga-keyboard"); diff --git a/drivers/input/keyboard/davinci_keyscan.c b/drivers/input/keyboard/davinci_keyscan.c index 4e4e453ea15..829753702b6 100644 --- a/drivers/input/keyboard/davinci_keyscan.c +++ b/drivers/input/keyboard/davinci_keyscan.c @@ -329,17 +329,7 @@ static struct platform_driver davinci_ks_driver = {  	.remove	= davinci_ks_remove,  }; -static int __init davinci_ks_init(void) -{ -	return platform_driver_probe(&davinci_ks_driver, davinci_ks_probe); -} -module_init(davinci_ks_init); - -static void __exit davinci_ks_exit(void) -{ -	platform_driver_unregister(&davinci_ks_driver); -} -module_exit(davinci_ks_exit); +module_platform_driver_probe(davinci_ks_driver, davinci_ks_probe);  MODULE_AUTHOR("Miguel Aguilar");  MODULE_DESCRIPTION("Texas Instruments DaVinci Key Scan Driver"); diff --git a/drivers/input/keyboard/imx_keypad.c b/drivers/input/keyboard/imx_keypad.c index 98f9113251d..03c8cc5cb6c 100644 --- a/drivers/input/keyboard/imx_keypad.c +++ b/drivers/input/keyboard/imx_keypad.c @@ -448,24 +448,17 @@ static int imx_keypad_probe(struct platform_device *pdev)  		return -EINVAL;  	} -	res = request_mem_region(res->start, resource_size(res), pdev->name); -	if (res == NULL) { -		dev_err(&pdev->dev, "failed to request I/O memory\n"); -		return -EBUSY; -	} - -	input_dev = input_allocate_device(); +	input_dev = devm_input_allocate_device(&pdev->dev);  	if (!input_dev) {  		dev_err(&pdev->dev, "failed to allocate the input device\n"); -		error = -ENOMEM; -		goto failed_rel_mem; +		return -ENOMEM;  	} -	keypad = kzalloc(sizeof(struct imx_keypad), GFP_KERNEL); +	keypad = devm_kzalloc(&pdev->dev, sizeof(struct imx_keypad), +			     GFP_KERNEL);  	if (!keypad) {  		dev_err(&pdev->dev, "not enough memory for driver data\n"); -		error = -ENOMEM; -		goto failed_free_input; +		return -ENOMEM;  	}  	keypad->input_dev = input_dev; @@ -475,18 +468,14 @@ static int imx_keypad_probe(struct platform_device *pdev)  	setup_timer(&keypad->check_matrix_timer,  		    imx_keypad_check_for_events, (unsigned long) keypad); -	keypad->mmio_base = ioremap(res->start, resource_size(res)); -	if (keypad->mmio_base == NULL) { -		dev_err(&pdev->dev, "failed to remap I/O memory\n"); -		error = -ENOMEM; -		goto failed_free_priv; -	} +	keypad->mmio_base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(keypad->mmio_base)) +		return PTR_ERR(keypad->mmio_base); -	keypad->clk = clk_get(&pdev->dev, NULL); +	keypad->clk = devm_clk_get(&pdev->dev, NULL);  	if (IS_ERR(keypad->clk)) {  		dev_err(&pdev->dev, "failed to get keypad clock\n"); -		error = PTR_ERR(keypad->clk); -		goto failed_unmap; +		return PTR_ERR(keypad->clk);  	}  	/* Init the Input device */ @@ -502,7 +491,7 @@ static int imx_keypad_probe(struct platform_device *pdev)  					   keypad->keycodes, input_dev);  	if (error) {  		dev_err(&pdev->dev, "failed to build keymap\n"); -		goto failed_clock_put; +		return error;  	}  	/* Search for rows and cols enabled */ @@ -527,61 +516,24 @@ static int imx_keypad_probe(struct platform_device *pdev)  	imx_keypad_inhibit(keypad);  	clk_disable_unprepare(keypad->clk); -	error = request_irq(irq, imx_keypad_irq_handler, 0, +	error = devm_request_irq(&pdev->dev, irq, imx_keypad_irq_handler, 0,  			    pdev->name, keypad);  	if (error) {  		dev_err(&pdev->dev, "failed to request IRQ\n"); -		goto failed_clock_put; +		return error;  	}  	/* Register the input device */  	error = input_register_device(input_dev);  	if (error) {  		dev_err(&pdev->dev, "failed to register input device\n"); -		goto failed_free_irq; +		return error;  	}  	platform_set_drvdata(pdev, keypad);  	device_init_wakeup(&pdev->dev, 1);  	return 0; - -failed_free_irq: -	free_irq(irq, pdev); -failed_clock_put: -	clk_put(keypad->clk); -failed_unmap: -	iounmap(keypad->mmio_base); -failed_free_priv: -	kfree(keypad); -failed_free_input: -	input_free_device(input_dev); -failed_rel_mem: -	release_mem_region(res->start, resource_size(res)); -	return error; -} - -static int imx_keypad_remove(struct platform_device *pdev) -{ -	struct imx_keypad *keypad = platform_get_drvdata(pdev); -	struct resource *res; - -	dev_dbg(&pdev->dev, ">%s\n", __func__); - -	platform_set_drvdata(pdev, NULL); - -	input_unregister_device(keypad->input_dev); - -	free_irq(keypad->irq, keypad); -	clk_put(keypad->clk); - -	iounmap(keypad->mmio_base); -	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	release_mem_region(res->start, resource_size(res)); - -	kfree(keypad); - -	return 0;  }  #ifdef CONFIG_PM_SLEEP @@ -640,7 +592,6 @@ static struct platform_driver imx_keypad_driver = {  		.of_match_table = of_match_ptr(imx_keypad_of_match),  	},  	.probe		= imx_keypad_probe, -	.remove		= imx_keypad_remove,  };  module_platform_driver(imx_keypad_driver); diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 0e6a8151fee..c7d505cce72 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -430,17 +430,7 @@ static struct platform_driver ske_keypad_driver = {  	.remove = ske_keypad_remove,  }; -static int __init ske_keypad_init(void) -{ -	return platform_driver_probe(&ske_keypad_driver, ske_keypad_probe); -} -module_init(ske_keypad_init); - -static void __exit ske_keypad_exit(void) -{ -	platform_driver_unregister(&ske_keypad_driver); -} -module_exit(ske_keypad_exit); +module_platform_driver_probe(ske_keypad_driver, ske_keypad_probe);  MODULE_LICENSE("GPL v2");  MODULE_AUTHOR("Naveen Kumar <naveen.gaddipati@stericsson.com> / Sundar Iyer <sundar.iyer@stericsson.com>"); diff --git a/drivers/input/keyboard/tegra-kbc.c b/drivers/input/keyboard/tegra-kbc.c index 0e138ebcc76..b46142f78ef 100644 --- a/drivers/input/keyboard/tegra-kbc.c +++ b/drivers/input/keyboard/tegra-kbc.c @@ -27,17 +27,19 @@  #include <linux/io.h>  #include <linux/interrupt.h>  #include <linux/of.h> +#include <linux/of_device.h>  #include <linux/clk.h>  #include <linux/slab.h>  #include <linux/input/matrix_keypad.h>  #include <linux/clk/tegra.h> +#include <linux/err.h> -#define KBC_MAX_GPIO	24  #define KBC_MAX_KPENT	8 -#define KBC_MAX_ROW	16 -#define KBC_MAX_COL	8 -#define KBC_MAX_KEY	(KBC_MAX_ROW * KBC_MAX_COL) +/* Maximum row/column supported by Tegra KBC yet  is 16x8 */ +#define KBC_MAX_GPIO	24 +/* Maximum keys supported by Tegra KBC yet is 16 x 8*/ +#define KBC_MAX_KEY	(16 * 8)  #define KBC_MAX_DEBOUNCE_CNT	0x3ffu @@ -80,6 +82,12 @@ enum tegra_pin_type {  	PIN_CFG_ROW,  }; +/* Tegra KBC hw support */ +struct tegra_kbc_hw_support { +	int max_rows; +	int max_columns; +}; +  struct tegra_kbc_pin_cfg {  	enum tegra_pin_type type;  	unsigned char num; @@ -108,6 +116,9 @@ struct tegra_kbc {  	u32 wakeup_key;  	struct timer_list timer;  	struct clk *clk; +	const struct tegra_kbc_hw_support *hw_support; +	int max_keys; +	int num_rows_and_columns;  };  static void tegra_kbc_report_released_keys(struct input_dev *input, @@ -204,11 +215,11 @@ static void tegra_kbc_report_keys(struct tegra_kbc *kbc)  	/*  	 * If the platform uses Fn keymaps, translate keys on a Fn keypress. -	 * Function keycodes are KBC_MAX_KEY apart from the plain keycodes. +	 * Function keycodes are max_keys apart from the plain keycodes.  	 */  	if (fn_keypress) {  		for (i = 0; i < num_down; i++) { -			scancodes[i] += KBC_MAX_KEY; +			scancodes[i] += kbc->max_keys;  			keycodes[i] = kbc->keycode[scancodes[i]];  		}  	} @@ -315,7 +326,7 @@ static void tegra_kbc_setup_wakekeys(struct tegra_kbc *kbc, bool filter)  	/* Either mask all keys or none. */  	rst_val = (filter && !kbc->wakeup) ? ~0 : 0; -	for (i = 0; i < KBC_MAX_ROW; i++) +	for (i = 0; i < kbc->hw_support->max_rows; i++)  		writel(rst_val, kbc->mmio + KBC_ROW0_MASK_0 + i * 4);  } @@ -452,7 +463,7 @@ static bool tegra_kbc_check_pin_cfg(const struct tegra_kbc *kbc,  		switch (pin_cfg->type) {  		case PIN_CFG_ROW: -			if (pin_cfg->num >= KBC_MAX_ROW) { +			if (pin_cfg->num >= kbc->hw_support->max_rows) {  				dev_err(kbc->dev,  					"pin_cfg[%d]: invalid row number %d\n",  					i, pin_cfg->num); @@ -462,7 +473,7 @@ static bool tegra_kbc_check_pin_cfg(const struct tegra_kbc *kbc,  			break;  		case PIN_CFG_COL: -			if (pin_cfg->num >= KBC_MAX_COL) { +			if (pin_cfg->num >= kbc->hw_support->max_columns) {  				dev_err(kbc->dev,  					"pin_cfg[%d]: invalid column number %d\n",  					i, pin_cfg->num); @@ -520,6 +531,18 @@ static int tegra_kbc_parse_dt(struct tegra_kbc *kbc)  	}  	num_cols = proplen / sizeof(u32); +	if (num_rows > kbc->hw_support->max_rows) { +		dev_err(kbc->dev, +			"Number of rows is more than supported by hardware\n"); +		return -EINVAL; +	} + +	if (num_cols > kbc->hw_support->max_columns) { +		dev_err(kbc->dev, +			"Number of cols is more than supported by hardware\n"); +		return -EINVAL; +	} +  	if (!of_get_property(np, "linux,keymap", &proplen)) {  		dev_err(kbc->dev, "property linux,keymap not found\n");  		return -ENOENT; @@ -532,7 +555,7 @@ static int tegra_kbc_parse_dt(struct tegra_kbc *kbc)  	}  	/* Set all pins as non-configured */ -	for (i = 0; i < KBC_MAX_GPIO; i++) +	for (i = 0; i < kbc->num_rows_and_columns; i++)  		kbc->pin_cfg[i].type = PIN_CFG_IGNORE;  	ret = of_property_read_u32_array(np, "nvidia,kbc-row-pins", @@ -562,6 +585,24 @@ static int tegra_kbc_parse_dt(struct tegra_kbc *kbc)  	return 0;  } +static const struct tegra_kbc_hw_support tegra20_kbc_hw_support = { +	.max_rows	= 16, +	.max_columns	= 8, +}; + +static const struct tegra_kbc_hw_support tegra11_kbc_hw_support = { +	.max_rows	= 11, +	.max_columns	= 8, +}; + +static const struct of_device_id tegra_kbc_of_match[] = { +	{ .compatible = "nvidia,tegra114-kbc", .data = &tegra11_kbc_hw_support}, +	{ .compatible = "nvidia,tegra30-kbc", .data = &tegra20_kbc_hw_support}, +	{ .compatible = "nvidia,tegra20-kbc", .data = &tegra20_kbc_hw_support}, +	{ }, +}; +MODULE_DEVICE_TABLE(of, tegra_kbc_of_match); +  static int tegra_kbc_probe(struct platform_device *pdev)  {  	struct tegra_kbc *kbc; @@ -570,7 +611,10 @@ static int tegra_kbc_probe(struct platform_device *pdev)  	int num_rows = 0;  	unsigned int debounce_cnt;  	unsigned int scan_time_rows; -	unsigned int keymap_rows = KBC_MAX_KEY; +	unsigned int keymap_rows; +	const struct of_device_id *match; + +	match = of_match_device(of_match_ptr(tegra_kbc_of_match), &pdev->dev);  	kbc = devm_kzalloc(&pdev->dev, sizeof(*kbc), GFP_KERNEL);  	if (!kbc) { @@ -579,6 +623,12 @@ static int tegra_kbc_probe(struct platform_device *pdev)  	}  	kbc->dev = &pdev->dev; +	kbc->hw_support = match->data; +	kbc->max_keys = kbc->hw_support->max_rows * +				kbc->hw_support->max_columns; +	kbc->num_rows_and_columns = kbc->hw_support->max_rows + +					kbc->hw_support->max_columns; +	keymap_rows = kbc->max_keys;  	spin_lock_init(&kbc->lock);  	err = tegra_kbc_parse_dt(kbc); @@ -608,11 +658,9 @@ static int tegra_kbc_probe(struct platform_device *pdev)  	setup_timer(&kbc->timer, tegra_kbc_keypress_timer, (unsigned long)kbc); -	kbc->mmio = devm_request_and_ioremap(&pdev->dev, res); -	if (!kbc->mmio) { -		dev_err(&pdev->dev, "Cannot request memregion/iomap address\n"); -		return -EBUSY; -	} +	kbc->mmio = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(kbc->mmio)) +		return PTR_ERR(kbc->mmio);  	kbc->clk = devm_clk_get(&pdev->dev, NULL);  	if (IS_ERR(kbc->clk)) { @@ -641,7 +689,8 @@ static int tegra_kbc_probe(struct platform_device *pdev)  		keymap_rows *= 2;  	err = matrix_keypad_build_keymap(kbc->keymap_data, NULL, -					 keymap_rows, KBC_MAX_COL, +					 keymap_rows, +					 kbc->hw_support->max_columns,  					 kbc->keycode, kbc->idev);  	if (err) {  		dev_err(&pdev->dev, "failed to setup keymap\n"); @@ -767,12 +816,6 @@ static int tegra_kbc_resume(struct device *dev)  static SIMPLE_DEV_PM_OPS(tegra_kbc_pm_ops, tegra_kbc_suspend, tegra_kbc_resume); -static const struct of_device_id tegra_kbc_of_match[] = { -	{ .compatible = "nvidia,tegra20-kbc", }, -	{ }, -}; -MODULE_DEVICE_TABLE(of, tegra_kbc_of_match); -  static struct platform_driver tegra_kbc_driver = {  	.probe		= tegra_kbc_probe,  	.driver	= { diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 259ef31abb1..af80928a46b 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -590,6 +590,16 @@ config INPUT_ADXL34X_SPI  	  To compile this driver as a module, choose M here: the  	  module will be called adxl34x-spi. +config INPUT_IMS_PCU +	tristate "IMS Passenger Control Unit driver" +	depends on USB +	depends on LEDS_CLASS +	help +	  Say Y here if you have system with IMS Rave Passenger Control Unit. + +	  To compile this driver as a module, choose M here: the module will be +	  called ims_pcu. +  config INPUT_CMA3000  	tristate "VTI CMA3000 Tri-axis accelerometer"  	help diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 1f1e1b109d9..d7fc17f11d7 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_INPUT_DM355EVM)		+= dm355evm_keys.o  obj-$(CONFIG_INPUT_GP2A)		+= gp2ap002a00f.o  obj-$(CONFIG_INPUT_GPIO_TILT_POLLED)	+= gpio_tilt_polled.o  obj-$(CONFIG_HP_SDC_RTC)		+= hp_sdc_rtc.o +obj-$(CONFIG_INPUT_IMS_PCU)		+= ims-pcu.o  obj-$(CONFIG_INPUT_IXP4XX_BEEPER)	+= ixp4xx-beeper.o  obj-$(CONFIG_INPUT_KEYSPAN_REMOTE)	+= keyspan_remote.o  obj-$(CONFIG_INPUT_KXTJ9)		+= kxtj9.o diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c index 29d2064c26f..e0f522516ef 100644 --- a/drivers/input/misc/ad714x-i2c.c +++ b/drivers/input/misc/ad714x-i2c.c @@ -13,7 +13,7 @@  #include <linux/pm.h>  #include "ad714x.h" -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP  static int ad714x_i2c_suspend(struct device *dev)  {  	return ad714x_disable(i2c_get_clientdata(to_i2c_client(dev))); diff --git a/drivers/input/misc/ad714x-spi.c b/drivers/input/misc/ad714x-spi.c index bdccca42d13..61891486067 100644 --- a/drivers/input/misc/ad714x-spi.c +++ b/drivers/input/misc/ad714x-spi.c @@ -16,7 +16,7 @@  #define AD714x_SPI_CMD_PREFIX      0xE000   /* bits 15:11 */  #define AD714x_SPI_READ            BIT(10) -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP  static int ad714x_spi_suspend(struct device *dev)  {  	return ad714x_disable(spi_get_drvdata(to_spi_device(dev))); diff --git a/drivers/input/misc/adxl34x-i2c.c b/drivers/input/misc/adxl34x-i2c.c index 535dda48cac..416f47ddcc9 100644 --- a/drivers/input/misc/adxl34x-i2c.c +++ b/drivers/input/misc/adxl34x-i2c.c @@ -105,7 +105,7 @@ static int adxl34x_i2c_remove(struct i2c_client *client)  	return adxl34x_remove(ac);  } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP  static int adxl34x_i2c_suspend(struct device *dev)  {  	struct i2c_client *client = to_i2c_client(dev); diff --git a/drivers/input/misc/adxl34x-spi.c b/drivers/input/misc/adxl34x-spi.c index ad5f40d37e4..76dc0679d3b 100644 --- a/drivers/input/misc/adxl34x-spi.c +++ b/drivers/input/misc/adxl34x-spi.c @@ -89,16 +89,16 @@ static int adxl34x_spi_probe(struct spi_device *spi)  static int adxl34x_spi_remove(struct spi_device *spi)  { -	struct adxl34x *ac = dev_get_drvdata(&spi->dev); +	struct adxl34x *ac = spi_get_drvdata(spi);  	return adxl34x_remove(ac);  } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP  static int adxl34x_spi_suspend(struct device *dev)  {  	struct spi_device *spi = to_spi_device(dev); -	struct adxl34x *ac = dev_get_drvdata(&spi->dev); +	struct adxl34x *ac = spi_get_drvdata(spi);  	adxl34x_suspend(ac); @@ -108,7 +108,7 @@ static int adxl34x_spi_suspend(struct device *dev)  static int adxl34x_spi_resume(struct device *dev)  {  	struct spi_device *spi = to_spi_device(dev); -	struct adxl34x *ac = dev_get_drvdata(&spi->dev); +	struct adxl34x *ac = spi_get_drvdata(spi);  	adxl34x_resume(ac); diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c new file mode 100644 index 00000000000..e204f26b001 --- /dev/null +++ b/drivers/input/misc/ims-pcu.c @@ -0,0 +1,1901 @@ +/* + * Driver for IMS Passenger Control Unit Devices + * + * Copyright (C) 2013 The IMS Company + * + * 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/completion.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/ihex.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/usb/input.h> +#include <linux/usb/cdc.h> +#include <asm/unaligned.h> + +#define IMS_PCU_KEYMAP_LEN		32 + +struct ims_pcu_buttons { +	struct input_dev *input; +	char name[32]; +	char phys[32]; +	unsigned short keymap[IMS_PCU_KEYMAP_LEN]; +}; + +struct ims_pcu_gamepad { +	struct input_dev *input; +	char name[32]; +	char phys[32]; +}; + +struct ims_pcu_backlight { +	struct led_classdev cdev; +	struct work_struct work; +	enum led_brightness desired_brightness; +	char name[32]; +}; + +#define IMS_PCU_PART_NUMBER_LEN		15 +#define IMS_PCU_SERIAL_NUMBER_LEN	8 +#define IMS_PCU_DOM_LEN			8 +#define IMS_PCU_FW_VERSION_LEN		(9 + 1) +#define IMS_PCU_BL_VERSION_LEN		(9 + 1) +#define IMS_PCU_BL_RESET_REASON_LEN	(2 + 1) + +#define IMS_PCU_BUF_SIZE		128 + +struct ims_pcu { +	struct usb_device *udev; +	struct device *dev; /* control interface's device, used for logging */ + +	unsigned int device_no; + +	bool bootloader_mode; + +	char part_number[IMS_PCU_PART_NUMBER_LEN]; +	char serial_number[IMS_PCU_SERIAL_NUMBER_LEN]; +	char date_of_manufacturing[IMS_PCU_DOM_LEN]; +	char fw_version[IMS_PCU_FW_VERSION_LEN]; +	char bl_version[IMS_PCU_BL_VERSION_LEN]; +	char reset_reason[IMS_PCU_BL_RESET_REASON_LEN]; +	int update_firmware_status; + +	struct usb_interface *ctrl_intf; + +	struct usb_endpoint_descriptor *ep_ctrl; +	struct urb *urb_ctrl; +	u8 *urb_ctrl_buf; +	dma_addr_t ctrl_dma; +	size_t max_ctrl_size; + +	struct usb_interface *data_intf; + +	struct usb_endpoint_descriptor *ep_in; +	struct urb *urb_in; +	u8 *urb_in_buf; +	dma_addr_t read_dma; +	size_t max_in_size; + +	struct usb_endpoint_descriptor *ep_out; +	u8 *urb_out_buf; +	size_t max_out_size; + +	u8 read_buf[IMS_PCU_BUF_SIZE]; +	u8 read_pos; +	u8 check_sum; +	bool have_stx; +	bool have_dle; + +	u8 cmd_buf[IMS_PCU_BUF_SIZE]; +	u8 ack_id; +	u8 expected_response; +	u8 cmd_buf_len; +	struct completion cmd_done; +	struct mutex cmd_mutex; + +	u32 fw_start_addr; +	u32 fw_end_addr; +	struct completion async_firmware_done; + +	struct ims_pcu_buttons buttons; +	struct ims_pcu_gamepad *gamepad; +	struct ims_pcu_backlight backlight; + +	bool setup_complete; /* Input and LED devices have been created */ +}; + + +/********************************************************************* + *             Buttons Input device support                          * + *********************************************************************/ + +static const unsigned short ims_pcu_keymap_1[] = { +	[1] = KEY_ATTENDANT_OFF, +	[2] = KEY_ATTENDANT_ON, +	[3] = KEY_LIGHTS_TOGGLE, +	[4] = KEY_VOLUMEUP, +	[5] = KEY_VOLUMEDOWN, +	[6] = KEY_INFO, +}; + +static const unsigned short ims_pcu_keymap_2[] = { +	[4] = KEY_VOLUMEUP, +	[5] = KEY_VOLUMEDOWN, +	[6] = KEY_INFO, +}; + +static const unsigned short ims_pcu_keymap_3[] = { +	[1] = KEY_HOMEPAGE, +	[2] = KEY_ATTENDANT_TOGGLE, +	[3] = KEY_LIGHTS_TOGGLE, +	[4] = KEY_VOLUMEUP, +	[5] = KEY_VOLUMEDOWN, +	[6] = KEY_DISPLAYTOGGLE, +	[18] = KEY_PLAYPAUSE, +}; + +static const unsigned short ims_pcu_keymap_4[] = { +	[1] = KEY_ATTENDANT_OFF, +	[2] = KEY_ATTENDANT_ON, +	[3] = KEY_LIGHTS_TOGGLE, +	[4] = KEY_VOLUMEUP, +	[5] = KEY_VOLUMEDOWN, +	[6] = KEY_INFO, +	[18] = KEY_PLAYPAUSE, +}; + +static const unsigned short ims_pcu_keymap_5[] = { +	[1] = KEY_ATTENDANT_OFF, +	[2] = KEY_ATTENDANT_ON, +	[3] = KEY_LIGHTS_TOGGLE, +}; + +struct ims_pcu_device_info { +	const unsigned short *keymap; +	size_t keymap_len; +	bool has_gamepad; +}; + +#define IMS_PCU_DEVINFO(_n, _gamepad)				\ +	[_n] = {						\ +		.keymap = ims_pcu_keymap_##_n,			\ +		.keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n),	\ +		.has_gamepad = _gamepad,			\ +	} + +static const struct ims_pcu_device_info ims_pcu_device_info[] = { +	IMS_PCU_DEVINFO(1, true), +	IMS_PCU_DEVINFO(2, true), +	IMS_PCU_DEVINFO(3, true), +	IMS_PCU_DEVINFO(4, true), +	IMS_PCU_DEVINFO(5, false), +}; + +static void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data) +{ +	struct ims_pcu_buttons *buttons = &pcu->buttons; +	struct input_dev *input = buttons->input; +	int i; + +	for (i = 0; i < 32; i++) { +		unsigned short keycode = buttons->keymap[i]; + +		if (keycode != KEY_RESERVED) +			input_report_key(input, keycode, data & (1UL << i)); +	} + +	input_sync(input); +} + +static int ims_pcu_setup_buttons(struct ims_pcu *pcu, +				 const unsigned short *keymap, +				 size_t keymap_len) +{ +	struct ims_pcu_buttons *buttons = &pcu->buttons; +	struct input_dev *input; +	int i; +	int error; + +	input = input_allocate_device(); +	if (!input) { +		dev_err(pcu->dev, +			"Not enough memory for input input device\n"); +		return -ENOMEM; +	} + +	snprintf(buttons->name, sizeof(buttons->name), +		 "IMS PCU#%d Button Interface", pcu->device_no); + +	usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys)); +	strlcat(buttons->phys, "/input0", sizeof(buttons->phys)); + +	memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len); + +	input->name = buttons->name; +	input->phys = buttons->phys; +	usb_to_input_id(pcu->udev, &input->id); +	input->dev.parent = &pcu->ctrl_intf->dev; + +	input->keycode = buttons->keymap; +	input->keycodemax = ARRAY_SIZE(buttons->keymap); +	input->keycodesize = sizeof(buttons->keymap[0]); + +	__set_bit(EV_KEY, input->evbit); +	for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++) +		__set_bit(buttons->keymap[i], input->keybit); +	__clear_bit(KEY_RESERVED, input->keybit); + +	error = input_register_device(input); +	if (error) { +		dev_err(pcu->dev, +			"Failed to register buttons input device: %d\n", +			error); +		input_free_device(input); +		return error; +	} + +	buttons->input = input; +	return 0; +} + +static void ims_pcu_destroy_buttons(struct ims_pcu *pcu) +{ +	struct ims_pcu_buttons *buttons = &pcu->buttons; + +	input_unregister_device(buttons->input); +} + + +/********************************************************************* + *             Gamepad Input device support                          * + *********************************************************************/ + +static void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data) +{ +	struct ims_pcu_gamepad *gamepad = pcu->gamepad; +	struct input_dev *input = gamepad->input; +	int x, y; + +	x = !!(data & (1 << 14)) - !!(data & (1 << 13)); +	y = !!(data & (1 << 12)) - !!(data & (1 << 11)); + +	input_report_abs(input, ABS_X, x); +	input_report_abs(input, ABS_Y, y); + +	input_report_key(input, BTN_A, data & (1 << 7)); +	input_report_key(input, BTN_B, data & (1 << 8)); +	input_report_key(input, BTN_X, data & (1 << 9)); +	input_report_key(input, BTN_Y, data & (1 << 10)); +	input_report_key(input, BTN_START, data & (1 << 15)); +	input_report_key(input, BTN_SELECT, data & (1 << 16)); + +	input_sync(input); +} + +static int ims_pcu_setup_gamepad(struct ims_pcu *pcu) +{ +	struct ims_pcu_gamepad *gamepad; +	struct input_dev *input; +	int error; + +	gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL); +	input = input_allocate_device(); +	if (!gamepad || !input) { +		dev_err(pcu->dev, +			"Not enough memory for gamepad device\n"); +		error = -ENOMEM; +		goto err_free_mem; +	} + +	gamepad->input = input; + +	snprintf(gamepad->name, sizeof(gamepad->name), +		 "IMS PCU#%d Gamepad Interface", pcu->device_no); + +	usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys)); +	strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys)); + +	input->name = gamepad->name; +	input->phys = gamepad->phys; +	usb_to_input_id(pcu->udev, &input->id); +	input->dev.parent = &pcu->ctrl_intf->dev; + +	__set_bit(EV_KEY, input->evbit); +	__set_bit(BTN_A, input->keybit); +	__set_bit(BTN_B, input->keybit); +	__set_bit(BTN_X, input->keybit); +	__set_bit(BTN_Y, input->keybit); +	__set_bit(BTN_START, input->keybit); +	__set_bit(BTN_SELECT, input->keybit); + +	__set_bit(EV_ABS, input->evbit); +	input_set_abs_params(input, ABS_X, -1, 1, 0, 0); +	input_set_abs_params(input, ABS_Y, -1, 1, 0, 0); + +	error = input_register_device(input); +	if (error) { +		dev_err(pcu->dev, +			"Failed to register gamepad input device: %d\n", +			error); +		goto err_free_mem; +	} + +	pcu->gamepad = gamepad; +	return 0; + +err_free_mem: +	input_free_device(input); +	kfree(gamepad); +	return -ENOMEM; +} + +static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu) +{ +	struct ims_pcu_gamepad *gamepad = pcu->gamepad; + +	input_unregister_device(gamepad->input); +	kfree(gamepad); +} + + +/********************************************************************* + *             PCU Communication protocol handling                   * + *********************************************************************/ + +#define IMS_PCU_PROTOCOL_STX		0x02 +#define IMS_PCU_PROTOCOL_ETX		0x03 +#define IMS_PCU_PROTOCOL_DLE		0x10 + +/* PCU commands */ +#define IMS_PCU_CMD_STATUS		0xa0 +#define IMS_PCU_CMD_PCU_RESET		0xa1 +#define IMS_PCU_CMD_RESET_REASON	0xa2 +#define IMS_PCU_CMD_SEND_BUTTONS	0xa3 +#define IMS_PCU_CMD_JUMP_TO_BTLDR	0xa4 +#define IMS_PCU_CMD_GET_INFO		0xa5 +#define IMS_PCU_CMD_SET_BRIGHTNESS	0xa6 +#define IMS_PCU_CMD_EEPROM		0xa7 +#define IMS_PCU_CMD_GET_FW_VERSION	0xa8 +#define IMS_PCU_CMD_GET_BL_VERSION	0xa9 +#define IMS_PCU_CMD_SET_INFO		0xab +#define IMS_PCU_CMD_GET_BRIGHTNESS	0xac +#define IMS_PCU_CMD_GET_DEVICE_ID	0xae +#define IMS_PCU_CMD_SPECIAL_INFO	0xb0 +#define IMS_PCU_CMD_BOOTLOADER		0xb1	/* Pass data to bootloader */ + +/* PCU responses */ +#define IMS_PCU_RSP_STATUS		0xc0 +#define IMS_PCU_RSP_PCU_RESET		0	/* Originally 0xc1 */ +#define IMS_PCU_RSP_RESET_REASON	0xc2 +#define IMS_PCU_RSP_SEND_BUTTONS	0xc3 +#define IMS_PCU_RSP_JUMP_TO_BTLDR	0	/* Originally 0xc4 */ +#define IMS_PCU_RSP_GET_INFO		0xc5 +#define IMS_PCU_RSP_SET_BRIGHTNESS	0xc6 +#define IMS_PCU_RSP_EEPROM		0xc7 +#define IMS_PCU_RSP_GET_FW_VERSION	0xc8 +#define IMS_PCU_RSP_GET_BL_VERSION	0xc9 +#define IMS_PCU_RSP_SET_INFO		0xcb +#define IMS_PCU_RSP_GET_BRIGHTNESS	0xcc +#define IMS_PCU_RSP_CMD_INVALID		0xcd +#define IMS_PCU_RSP_GET_DEVICE_ID	0xce +#define IMS_PCU_RSP_SPECIAL_INFO	0xd0 +#define IMS_PCU_RSP_BOOTLOADER		0xd1	/* Bootloader response */ + +#define IMS_PCU_RSP_EVNT_BUTTONS	0xe0	/* Unsolicited, button state */ +#define IMS_PCU_GAMEPAD_MASK		0x0001ff80UL	/* Bits 7 through 16 */ + + +#define IMS_PCU_MIN_PACKET_LEN		3 +#define IMS_PCU_DATA_OFFSET		2 + +#define IMS_PCU_CMD_WRITE_TIMEOUT	100 /* msec */ +#define IMS_PCU_CMD_RESPONSE_TIMEOUT	500 /* msec */ + +static void ims_pcu_report_events(struct ims_pcu *pcu) +{ +	u32 data = get_unaligned_be32(&pcu->read_buf[3]); + +	ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK); +	if (pcu->gamepad) +		ims_pcu_gamepad_report(pcu, data); +} + +static void ims_pcu_handle_response(struct ims_pcu *pcu) +{ +	switch (pcu->read_buf[0]) { +	case IMS_PCU_RSP_EVNT_BUTTONS: +		if (likely(pcu->setup_complete)) +			ims_pcu_report_events(pcu); +		break; + +	default: +		/* +		 * See if we got command completion. +		 * If both the sequence and response code match save +		 * the data and signal completion. +		 */ +		if (pcu->read_buf[0] == pcu->expected_response && +		    pcu->read_buf[1] == pcu->ack_id - 1) { + +			memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos); +			pcu->cmd_buf_len = pcu->read_pos; +			complete(&pcu->cmd_done); +		} +		break; +	} +} + +static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) +{ +	int i; + +	for (i = 0; i < urb->actual_length; i++) { +		u8 data = pcu->urb_in_buf[i]; + +		/* Skip everything until we get Start Xmit */ +		if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX) +			continue; + +		if (pcu->have_dle) { +			pcu->have_dle = false; +			pcu->read_buf[pcu->read_pos++] = data; +			pcu->check_sum += data; +			continue; +		} + +		switch (data) { +		case IMS_PCU_PROTOCOL_STX: +			if (pcu->have_stx) +				dev_warn(pcu->dev, +					 "Unexpected STX at byte %d, discarding old data\n", +					 pcu->read_pos); +			pcu->have_stx = true; +			pcu->have_dle = false; +			pcu->read_pos = 0; +			pcu->check_sum = 0; +			break; + +		case IMS_PCU_PROTOCOL_DLE: +			pcu->have_dle = true; +			break; + +		case IMS_PCU_PROTOCOL_ETX: +			if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) { +				dev_warn(pcu->dev, +					 "Short packet received (%d bytes), ignoring\n", +					 pcu->read_pos); +			} else if (pcu->check_sum != 0) { +				dev_warn(pcu->dev, +					 "Invalid checksum in packet (%d bytes), ignoring\n", +					 pcu->read_pos); +			} else { +				ims_pcu_handle_response(pcu); +			} + +			pcu->have_stx = false; +			pcu->have_dle = false; +			pcu->read_pos = 0; +			break; + +		default: +			pcu->read_buf[pcu->read_pos++] = data; +			pcu->check_sum += data; +			break; +		} +	} +} + +static bool ims_pcu_byte_needs_escape(u8 byte) +{ +	return byte == IMS_PCU_PROTOCOL_STX || +	       byte == IMS_PCU_PROTOCOL_ETX || +	       byte == IMS_PCU_PROTOCOL_DLE; +} + +static int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu, +				  u8 command, int chunk, int len) +{ +	int error; + +	error = usb_bulk_msg(pcu->udev, +			     usb_sndbulkpipe(pcu->udev, +					     pcu->ep_out->bEndpointAddress), +			     pcu->urb_out_buf, len, +			     NULL, IMS_PCU_CMD_WRITE_TIMEOUT); +	if (error < 0) { +		dev_dbg(pcu->dev, +			"Sending 0x%02x command failed at chunk %d: %d\n", +			command, chunk, error); +		return error; +	} + +	return 0; +} + +static int ims_pcu_send_command(struct ims_pcu *pcu, +				u8 command, const u8 *data, int len) +{ +	int count = 0; +	int chunk = 0; +	int delta; +	int i; +	int error; +	u8 csum = 0; +	u8 ack_id; + +	pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX; + +	/* We know the command need not be escaped */ +	pcu->urb_out_buf[count++] = command; +	csum += command; + +	ack_id = pcu->ack_id++; +	if (ack_id == 0xff) +		ack_id = pcu->ack_id++; + +	if (ims_pcu_byte_needs_escape(ack_id)) +		pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; + +	pcu->urb_out_buf[count++] = ack_id; +	csum += ack_id; + +	for (i = 0; i < len; i++) { + +		delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1; +		if (count + delta >= pcu->max_out_size) { +			error = ims_pcu_send_cmd_chunk(pcu, command, +						       ++chunk, count); +			if (error) +				return error; + +			count = 0; +		} + +		if (delta == 2) +			pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; + +		pcu->urb_out_buf[count++] = data[i]; +		csum += data[i]; +	} + +	csum = 1 + ~csum; + +	delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2; +	if (count + delta >= pcu->max_out_size) { +		error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); +		if (error) +			return error; + +		count = 0; +	} + +	if (delta == 3) +		pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; + +	pcu->urb_out_buf[count++] = csum; +	pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX; + +	return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); +} + +static int __ims_pcu_execute_command(struct ims_pcu *pcu, +				     u8 command, const void *data, size_t len, +				     u8 expected_response, int response_time) +{ +	int error; + +	pcu->expected_response = expected_response; +	init_completion(&pcu->cmd_done); + +	error = ims_pcu_send_command(pcu, command, data, len); +	if (error) +		return error; + +	if (expected_response && +	    !wait_for_completion_timeout(&pcu->cmd_done, +					 msecs_to_jiffies(response_time))) { +		dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command); +		return -ETIMEDOUT; +	} + +	return 0; +} + +#define ims_pcu_execute_command(pcu, code, data, len)			\ +	__ims_pcu_execute_command(pcu,					\ +				  IMS_PCU_CMD_##code, data, len,	\ +				  IMS_PCU_RSP_##code,			\ +				  IMS_PCU_CMD_RESPONSE_TIMEOUT) + +#define ims_pcu_execute_query(pcu, code)				\ +	ims_pcu_execute_command(pcu, code, NULL, 0) + +/* Bootloader commands */ +#define IMS_PCU_BL_CMD_QUERY_DEVICE	0xa1 +#define IMS_PCU_BL_CMD_UNLOCK_CONFIG	0xa2 +#define IMS_PCU_BL_CMD_ERASE_APP	0xa3 +#define IMS_PCU_BL_CMD_PROGRAM_DEVICE	0xa4 +#define IMS_PCU_BL_CMD_PROGRAM_COMPLETE	0xa5 +#define IMS_PCU_BL_CMD_READ_APP		0xa6 +#define IMS_PCU_BL_CMD_RESET_DEVICE	0xa7 +#define IMS_PCU_BL_CMD_LAUNCH_APP	0xa8 + +/* Bootloader commands */ +#define IMS_PCU_BL_RSP_QUERY_DEVICE	0xc1 +#define IMS_PCU_BL_RSP_UNLOCK_CONFIG	0xc2 +#define IMS_PCU_BL_RSP_ERASE_APP	0xc3 +#define IMS_PCU_BL_RSP_PROGRAM_DEVICE	0xc4 +#define IMS_PCU_BL_RSP_PROGRAM_COMPLETE	0xc5 +#define IMS_PCU_BL_RSP_READ_APP		0xc6 +#define IMS_PCU_BL_RSP_RESET_DEVICE	0	/* originally 0xa7 */ +#define IMS_PCU_BL_RSP_LAUNCH_APP	0	/* originally 0xa8 */ + +#define IMS_PCU_BL_DATA_OFFSET		3 + +static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu, +				        u8 command, const void *data, size_t len, +				        u8 expected_response, int response_time) +{ +	int error; + +	pcu->cmd_buf[0] = command; +	if (data) +		memcpy(&pcu->cmd_buf[1], data, len); + +	error = __ims_pcu_execute_command(pcu, +				IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1, +				expected_response ? IMS_PCU_RSP_BOOTLOADER : 0, +				response_time); +	if (error) { +		dev_err(pcu->dev, +			"Failure when sending 0x%02x command to bootloader, error: %d\n", +			pcu->cmd_buf[0], error); +		return error; +	} + +	if (expected_response && pcu->cmd_buf[2] != expected_response) { +		dev_err(pcu->dev, +			"Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n", +			pcu->cmd_buf[2], expected_response); +		return -EINVAL; +	} + +	return 0; +} + +#define ims_pcu_execute_bl_command(pcu, code, data, len, timeout)	\ +	__ims_pcu_execute_bl_command(pcu,				\ +				     IMS_PCU_BL_CMD_##code, data, len,	\ +				     IMS_PCU_BL_RSP_##code, timeout)	\ + +#define IMS_PCU_INFO_PART_OFFSET	2 +#define IMS_PCU_INFO_DOM_OFFSET		17 +#define IMS_PCU_INFO_SERIAL_OFFSET	25 + +#define IMS_PCU_SET_INFO_SIZE		31 + +static int ims_pcu_get_info(struct ims_pcu *pcu) +{ +	int error; + +	error = ims_pcu_execute_query(pcu, GET_INFO); +	if (error) { +		dev_err(pcu->dev, +			"GET_INFO command failed, error: %d\n", error); +		return error; +	} + +	memcpy(pcu->part_number, +	       &pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], +	       sizeof(pcu->part_number)); +	memcpy(pcu->date_of_manufacturing, +	       &pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], +	       sizeof(pcu->date_of_manufacturing)); +	memcpy(pcu->serial_number, +	       &pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], +	       sizeof(pcu->serial_number)); + +	return 0; +} + +static int ims_pcu_set_info(struct ims_pcu *pcu) +{ +	int error; + +	memcpy(&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], +	       pcu->part_number, sizeof(pcu->part_number)); +	memcpy(&pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], +	       pcu->date_of_manufacturing, sizeof(pcu->date_of_manufacturing)); +	memcpy(&pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], +	       pcu->serial_number, sizeof(pcu->serial_number)); + +	error = ims_pcu_execute_command(pcu, SET_INFO, +					&pcu->cmd_buf[IMS_PCU_DATA_OFFSET], +					IMS_PCU_SET_INFO_SIZE); +	if (error) { +		dev_err(pcu->dev, +			"Failed to update device information, error: %d\n", +			error); +		return error; +	} + +	return 0; +} + +static int ims_pcu_switch_to_bootloader(struct ims_pcu *pcu) +{ +	int error; + +	/* Execute jump to the bootoloader */ +	error = ims_pcu_execute_command(pcu, JUMP_TO_BTLDR, NULL, 0); +	if (error) { +		dev_err(pcu->dev, +			"Failure when sending JUMP TO BOOLTLOADER command, error: %d\n", +			error); +		return error; +	} + +	return 0; +} + +/********************************************************************* + *             Firmware Update handling                              * + *********************************************************************/ + +#define IMS_PCU_FIRMWARE_NAME	"imspcu.fw" + +struct ims_pcu_flash_fmt { +	__le32 addr; +	u8 len; +	u8 data[]; +}; + +static unsigned int ims_pcu_count_fw_records(const struct firmware *fw) +{ +	const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; +	unsigned int count = 0; + +	while (rec) { +		count++; +		rec = ihex_next_binrec(rec); +	} + +	return count; +} + +static int ims_pcu_verify_block(struct ims_pcu *pcu, +				u32 addr, u8 len, const u8 *data) +{ +	struct ims_pcu_flash_fmt *fragment; +	int error; + +	fragment = (void *)&pcu->cmd_buf[1]; +	put_unaligned_le32(addr, &fragment->addr); +	fragment->len = len; + +	error = ims_pcu_execute_bl_command(pcu, READ_APP, NULL, 5, +					IMS_PCU_CMD_RESPONSE_TIMEOUT); +	if (error) { +		dev_err(pcu->dev, +			"Failed to retrieve block at 0x%08x, len %d, error: %d\n", +			addr, len, error); +		return error; +	} + +	fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET]; +	if (get_unaligned_le32(&fragment->addr) != addr || +	    fragment->len != len) { +		dev_err(pcu->dev, +			"Wrong block when retrieving 0x%08x (0x%08x), len %d (%d)\n", +			addr, get_unaligned_le32(&fragment->addr), +			len, fragment->len); +		return -EINVAL; +	} + +	if (memcmp(fragment->data, data, len)) { +		dev_err(pcu->dev, +			"Mismatch in block at 0x%08x, len %d\n", +			addr, len); +		return -EINVAL; +	} + +	return 0; +} + +static int ims_pcu_flash_firmware(struct ims_pcu *pcu, +				  const struct firmware *fw, +				  unsigned int n_fw_records) +{ +	const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; +	struct ims_pcu_flash_fmt *fragment; +	unsigned int count = 0; +	u32 addr; +	u8 len; +	int error; + +	error = ims_pcu_execute_bl_command(pcu, ERASE_APP, NULL, 0, 2000); +	if (error) { +		dev_err(pcu->dev, +			"Failed to erase application image, error: %d\n", +			error); +		return error; +	} + +	while (rec) { +		/* +		 * The firmware format is messed up for some reason. +		 * The address twice that of what is needed for some +		 * reason and we end up overwriting half of the data +		 * with the next record. +		 */ +		addr = be32_to_cpu(rec->addr) / 2; +		len = be16_to_cpu(rec->len); + +		fragment = (void *)&pcu->cmd_buf[1]; +		put_unaligned_le32(addr, &fragment->addr); +		fragment->len = len; +		memcpy(fragment->data, rec->data, len); + +		error = ims_pcu_execute_bl_command(pcu, PROGRAM_DEVICE, +						NULL, len + 5, +						IMS_PCU_CMD_RESPONSE_TIMEOUT); +		if (error) { +			dev_err(pcu->dev, +				"Failed to write block at 0x%08x, len %d, error: %d\n", +				addr, len, error); +			return error; +		} + +		if (addr >= pcu->fw_start_addr && addr < pcu->fw_end_addr) { +			error = ims_pcu_verify_block(pcu, addr, len, rec->data); +			if (error) +				return error; +		} + +		count++; +		pcu->update_firmware_status = (count * 100) / n_fw_records; + +		rec = ihex_next_binrec(rec); +	} + +	error = ims_pcu_execute_bl_command(pcu, PROGRAM_COMPLETE, +					    NULL, 0, 2000); +	if (error) +		dev_err(pcu->dev, +			"Failed to send PROGRAM_COMPLETE, error: %d\n", +			error); + +	return 0; +} + +static int ims_pcu_handle_firmware_update(struct ims_pcu *pcu, +					  const struct firmware *fw) +{ +	unsigned int n_fw_records; +	int retval; + +	dev_info(pcu->dev, "Updating firmware %s, size: %zu\n", +		 IMS_PCU_FIRMWARE_NAME, fw->size); + +	n_fw_records = ims_pcu_count_fw_records(fw); + +	retval = ims_pcu_flash_firmware(pcu, fw, n_fw_records); +	if (retval) +		goto out; + +	retval = ims_pcu_execute_bl_command(pcu, LAUNCH_APP, NULL, 0, 0); +	if (retval) +		dev_err(pcu->dev, +			"Failed to start application image, error: %d\n", +			retval); + +out: +	pcu->update_firmware_status = retval; +	sysfs_notify(&pcu->dev->kobj, NULL, "update_firmware_status"); +	return retval; +} + +static void ims_pcu_process_async_firmware(const struct firmware *fw, +					   void *context) +{ +	struct ims_pcu *pcu = context; +	int error; + +	if (!fw) { +		dev_err(pcu->dev, "Failed to get firmware %s\n", +			IMS_PCU_FIRMWARE_NAME); +		goto out; +	} + +	error = ihex_validate_fw(fw); +	if (error) { +		dev_err(pcu->dev, "Firmware %s is invalid\n", +			IMS_PCU_FIRMWARE_NAME); +		goto out; +	} + +	mutex_lock(&pcu->cmd_mutex); +	ims_pcu_handle_firmware_update(pcu, fw); +	mutex_unlock(&pcu->cmd_mutex); + +	release_firmware(fw); + +out: +	complete(&pcu->async_firmware_done); +} + +/********************************************************************* + *             Backlight LED device support                          * + *********************************************************************/ + +#define IMS_PCU_MAX_BRIGHTNESS		31998 + +static void ims_pcu_backlight_work(struct work_struct *work) +{ +	struct ims_pcu_backlight *backlight = +			container_of(work, struct ims_pcu_backlight, work); +	struct ims_pcu *pcu = +			container_of(backlight, struct ims_pcu, backlight); +	int desired_brightness = backlight->desired_brightness; +	__le16 br_val = cpu_to_le16(desired_brightness); +	int error; + +	mutex_lock(&pcu->cmd_mutex); + +	error = ims_pcu_execute_command(pcu, SET_BRIGHTNESS, +					&br_val, sizeof(br_val)); +	if (error && error != -ENODEV) +		dev_warn(pcu->dev, +			 "Failed to set desired brightness %u, error: %d\n", +			 desired_brightness, error); + +	mutex_unlock(&pcu->cmd_mutex); +} + +static void ims_pcu_backlight_set_brightness(struct led_classdev *cdev, +					     enum led_brightness value) +{ +	struct ims_pcu_backlight *backlight = +			container_of(cdev, struct ims_pcu_backlight, cdev); + +	backlight->desired_brightness = value; +	schedule_work(&backlight->work); +} + +static enum led_brightness +ims_pcu_backlight_get_brightness(struct led_classdev *cdev) +{ +	struct ims_pcu_backlight *backlight = +			container_of(cdev, struct ims_pcu_backlight, cdev); +	struct ims_pcu *pcu = +			container_of(backlight, struct ims_pcu, backlight); +	int brightness; +	int error; + +	mutex_lock(&pcu->cmd_mutex); + +	error = ims_pcu_execute_query(pcu, GET_BRIGHTNESS); +	if (error) { +		dev_warn(pcu->dev, +			 "Failed to get current brightness, error: %d\n", +			 error); +		/* Assume the LED is OFF */ +		brightness = LED_OFF; +	} else { +		brightness = +			get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); +	} + +	mutex_unlock(&pcu->cmd_mutex); + +	return brightness; +} + +static int ims_pcu_setup_backlight(struct ims_pcu *pcu) +{ +	struct ims_pcu_backlight *backlight = &pcu->backlight; +	int error; + +	INIT_WORK(&backlight->work, ims_pcu_backlight_work); +	snprintf(backlight->name, sizeof(backlight->name), +		 "pcu%d::kbd_backlight", pcu->device_no); + +	backlight->cdev.name = backlight->name; +	backlight->cdev.max_brightness = IMS_PCU_MAX_BRIGHTNESS; +	backlight->cdev.brightness_get = ims_pcu_backlight_get_brightness; +	backlight->cdev.brightness_set = ims_pcu_backlight_set_brightness; + +	error = led_classdev_register(pcu->dev, &backlight->cdev); +	if (error) { +		dev_err(pcu->dev, +			"Failed to register backlight LED device, error: %d\n", +			error); +		return error; +	} + +	return 0; +} + +static void ims_pcu_destroy_backlight(struct ims_pcu *pcu) +{ +	struct ims_pcu_backlight *backlight = &pcu->backlight; + +	led_classdev_unregister(&backlight->cdev); +	cancel_work_sync(&backlight->work); +} + + +/********************************************************************* + *             Sysfs attributes handling                             * + *********************************************************************/ + +struct ims_pcu_attribute { +	struct device_attribute dattr; +	size_t field_offset; +	int field_length; +}; + +static ssize_t ims_pcu_attribute_show(struct device *dev, +				      struct device_attribute *dattr, +				      char *buf) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	struct ims_pcu_attribute *attr = +			container_of(dattr, struct ims_pcu_attribute, dattr); +	char *field = (char *)pcu + attr->field_offset; + +	return scnprintf(buf, PAGE_SIZE, "%.*s\n", attr->field_length, field); +} + +static ssize_t ims_pcu_attribute_store(struct device *dev, +				       struct device_attribute *dattr, +				       const char *buf, size_t count) +{ + +	struct usb_interface *intf = to_usb_interface(dev); +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	struct ims_pcu_attribute *attr = +			container_of(dattr, struct ims_pcu_attribute, dattr); +	char *field = (char *)pcu + attr->field_offset; +	size_t data_len; +	int error; + +	if (count > attr->field_length) +		return -EINVAL; + +	data_len = strnlen(buf, attr->field_length); +	if (data_len > attr->field_length) +		return -EINVAL; + +	error = mutex_lock_interruptible(&pcu->cmd_mutex); +	if (error) +		return error; + +	memset(field, 0, attr->field_length); +	memcpy(field, buf, data_len); + +	error = ims_pcu_set_info(pcu); + +	/* +	 * Even if update failed, let's fetch the info again as we just +	 * clobbered one of the fields. +	 */ +	ims_pcu_get_info(pcu); + +	mutex_unlock(&pcu->cmd_mutex); + +	return error < 0 ? error : count; +} + +#define IMS_PCU_ATTR(_field, _mode)					\ +struct ims_pcu_attribute ims_pcu_attr_##_field = {			\ +	.dattr = __ATTR(_field, _mode,					\ +			ims_pcu_attribute_show,				\ +			ims_pcu_attribute_store),			\ +	.field_offset = offsetof(struct ims_pcu, _field),		\ +	.field_length = sizeof(((struct ims_pcu *)NULL)->_field),	\ +} + +#define IMS_PCU_RO_ATTR(_field)						\ +		IMS_PCU_ATTR(_field, S_IRUGO) +#define IMS_PCU_RW_ATTR(_field)						\ +		IMS_PCU_ATTR(_field, S_IRUGO | S_IWUSR) + +static IMS_PCU_RW_ATTR(part_number); +static IMS_PCU_RW_ATTR(serial_number); +static IMS_PCU_RW_ATTR(date_of_manufacturing); + +static IMS_PCU_RO_ATTR(fw_version); +static IMS_PCU_RO_ATTR(bl_version); +static IMS_PCU_RO_ATTR(reset_reason); + +static ssize_t ims_pcu_reset_device(struct device *dev, +				    struct device_attribute *dattr, +				    const char *buf, size_t count) +{ +	static const u8 reset_byte = 1; +	struct usb_interface *intf = to_usb_interface(dev); +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	int value; +	int error; + +	error = kstrtoint(buf, 0, &value); +	if (error) +		return error; + +	if (value != 1) +		return -EINVAL; + +	dev_info(pcu->dev, "Attempting to reset device\n"); + +	error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1); +	if (error) { +		dev_info(pcu->dev, +			 "Failed to reset device, error: %d\n", +			 error); +		return error; +	} + +	return count; +} + +static DEVICE_ATTR(reset_device, S_IWUSR, NULL, ims_pcu_reset_device); + +static ssize_t ims_pcu_update_firmware_store(struct device *dev, +					     struct device_attribute *dattr, +					     const char *buf, size_t count) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	const struct firmware *fw = NULL; +	int value; +	int error; + +	error = kstrtoint(buf, 0, &value); +	if (error) +		return error; + +	if (value != 1) +		return -EINVAL; + +	error = mutex_lock_interruptible(&pcu->cmd_mutex); +	if (error) +		return error; + +	error = request_ihex_firmware(&fw, IMS_PCU_FIRMWARE_NAME, pcu->dev); +	if (error) { +		dev_err(pcu->dev, "Failed to request firmware %s, error: %d\n", +			IMS_PCU_FIRMWARE_NAME, error); +		goto out; +	} + +	/* +	 * If we are already in bootloader mode we can proceed with +	 * flashing the firmware. +	 * +	 * If we are in application mode, then we need to switch into +	 * bootloader mode, which will cause the device to disconnect +	 * and reconnect as different device. +	 */ +	if (pcu->bootloader_mode) +		error = ims_pcu_handle_firmware_update(pcu, fw); +	else +		error = ims_pcu_switch_to_bootloader(pcu); + +	release_firmware(fw); + +out: +	mutex_unlock(&pcu->cmd_mutex); +	return error ?: count; +} + +static DEVICE_ATTR(update_firmware, S_IWUSR, +		   NULL, ims_pcu_update_firmware_store); + +static ssize_t +ims_pcu_update_firmware_status_show(struct device *dev, +				    struct device_attribute *dattr, +				    char *buf) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct ims_pcu *pcu = usb_get_intfdata(intf); + +	return scnprintf(buf, PAGE_SIZE, "%d\n", pcu->update_firmware_status); +} + +static DEVICE_ATTR(update_firmware_status, S_IRUGO, +		   ims_pcu_update_firmware_status_show, NULL); + +static struct attribute *ims_pcu_attrs[] = { +	&ims_pcu_attr_part_number.dattr.attr, +	&ims_pcu_attr_serial_number.dattr.attr, +	&ims_pcu_attr_date_of_manufacturing.dattr.attr, +	&ims_pcu_attr_fw_version.dattr.attr, +	&ims_pcu_attr_bl_version.dattr.attr, +	&ims_pcu_attr_reset_reason.dattr.attr, +	&dev_attr_reset_device.attr, +	&dev_attr_update_firmware.attr, +	&dev_attr_update_firmware_status.attr, +	NULL +}; + +static umode_t ims_pcu_is_attr_visible(struct kobject *kobj, +				       struct attribute *attr, int n) +{ +	struct device *dev = container_of(kobj, struct device, kobj); +	struct usb_interface *intf = to_usb_interface(dev); +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	umode_t mode = attr->mode; + +	if (pcu->bootloader_mode) { +		if (attr != &dev_attr_update_firmware_status.attr && +		    attr != &dev_attr_update_firmware.attr && +		    attr != &dev_attr_reset_device.attr) { +			mode = 0; +		} +	} else { +		if (attr == &dev_attr_update_firmware_status.attr) +			mode = 0; +	} + +	return mode; +} + +static struct attribute_group ims_pcu_attr_group = { +	.is_visible	= ims_pcu_is_attr_visible, +	.attrs		= ims_pcu_attrs, +}; + +static void ims_pcu_irq(struct urb *urb) +{ +	struct ims_pcu *pcu = urb->context; +	int retval, status; + +	status = urb->status; + +	switch (status) { +	case 0: +		/* success */ +		break; +	case -ECONNRESET: +	case -ENOENT: +	case -ESHUTDOWN: +		/* this urb is terminated, clean up */ +		dev_dbg(pcu->dev, "%s - urb shutting down with status: %d\n", +			__func__, status); +		return; +	default: +		dev_dbg(pcu->dev, "%s - nonzero urb status received: %d\n", +			__func__, status); +		goto exit; +	} + +	dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__, +		urb->actual_length, urb->actual_length, pcu->urb_in_buf); + +	if (urb == pcu->urb_in) +		ims_pcu_process_data(pcu, urb); + +exit: +	retval = usb_submit_urb(urb, GFP_ATOMIC); +	if (retval && retval != -ENODEV) +		dev_err(pcu->dev, "%s - usb_submit_urb failed with result %d\n", +			__func__, retval); +} + +static int ims_pcu_buffers_alloc(struct ims_pcu *pcu) +{ +	int error; + +	pcu->urb_in_buf = usb_alloc_coherent(pcu->udev, pcu->max_in_size, +					     GFP_KERNEL, &pcu->read_dma); +	if (!pcu->urb_in_buf) { +		dev_err(pcu->dev, +			"Failed to allocate memory for read buffer\n"); +		return -ENOMEM; +	} + +	pcu->urb_in = usb_alloc_urb(0, GFP_KERNEL); +	if (!pcu->urb_in) { +		dev_err(pcu->dev, "Failed to allocate input URB\n"); +		error = -ENOMEM; +		goto err_free_urb_in_buf; +	} + +	pcu->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; +	pcu->urb_in->transfer_dma = pcu->read_dma; + +	usb_fill_bulk_urb(pcu->urb_in, pcu->udev, +			  usb_rcvbulkpipe(pcu->udev, +					  pcu->ep_in->bEndpointAddress), +			  pcu->urb_in_buf, pcu->max_in_size, +			  ims_pcu_irq, pcu); + +	/* +	 * We are using usb_bulk_msg() for sending so there is no point +	 * in allocating memory with usb_alloc_coherent(). +	 */ +	pcu->urb_out_buf = kmalloc(pcu->max_out_size, GFP_KERNEL); +	if (!pcu->urb_out_buf) { +		dev_err(pcu->dev, "Failed to allocate memory for write buffer\n"); +		error = -ENOMEM; +		goto err_free_in_urb; +	} + +	pcu->urb_ctrl_buf = usb_alloc_coherent(pcu->udev, pcu->max_ctrl_size, +					       GFP_KERNEL, &pcu->ctrl_dma); +	if (!pcu->urb_ctrl_buf) { +		dev_err(pcu->dev, +			"Failed to allocate memory for read buffer\n"); +		goto err_free_urb_out_buf; +	} + +	pcu->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL); +	if (!pcu->urb_ctrl) { +		dev_err(pcu->dev, "Failed to allocate input URB\n"); +		error = -ENOMEM; +		goto err_free_urb_ctrl_buf; +	} + +	pcu->urb_ctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; +	pcu->urb_ctrl->transfer_dma = pcu->ctrl_dma; + +	usb_fill_int_urb(pcu->urb_ctrl, pcu->udev, +			  usb_rcvintpipe(pcu->udev, +					 pcu->ep_ctrl->bEndpointAddress), +			  pcu->urb_ctrl_buf, pcu->max_ctrl_size, +			  ims_pcu_irq, pcu, pcu->ep_ctrl->bInterval); + +	return 0; + +err_free_urb_ctrl_buf: +	usb_free_coherent(pcu->udev, pcu->max_ctrl_size, +			  pcu->urb_ctrl_buf, pcu->ctrl_dma); +err_free_urb_out_buf: +	kfree(pcu->urb_out_buf); +err_free_in_urb: +	usb_free_urb(pcu->urb_in); +err_free_urb_in_buf: +	usb_free_coherent(pcu->udev, pcu->max_in_size, +			  pcu->urb_in_buf, pcu->read_dma); +	return error; +} + +static void ims_pcu_buffers_free(struct ims_pcu *pcu) +{ +	usb_kill_urb(pcu->urb_in); +	usb_free_urb(pcu->urb_in); + +	usb_free_coherent(pcu->udev, pcu->max_out_size, +			  pcu->urb_in_buf, pcu->read_dma); + +	kfree(pcu->urb_out_buf); + +	usb_kill_urb(pcu->urb_ctrl); +	usb_free_urb(pcu->urb_ctrl); + +	usb_free_coherent(pcu->udev, pcu->max_ctrl_size, +			  pcu->urb_ctrl_buf, pcu->ctrl_dma); +} + +static const struct usb_cdc_union_desc * +ims_pcu_get_cdc_union_desc(struct usb_interface *intf) +{ +	const void *buf = intf->altsetting->extra; +	size_t buflen = intf->altsetting->extralen; +	struct usb_cdc_union_desc *union_desc; + +	if (!buf) { +		dev_err(&intf->dev, "Missing descriptor data\n"); +		return NULL; +	} + +	if (!buflen) { +		dev_err(&intf->dev, "Zero length descriptor\n"); +		return NULL; +	} + +	while (buflen > 0) { +		union_desc = (struct usb_cdc_union_desc *)buf; + +		if (union_desc->bDescriptorType == USB_DT_CS_INTERFACE && +		    union_desc->bDescriptorSubType == USB_CDC_UNION_TYPE) { +			dev_dbg(&intf->dev, "Found union header\n"); +			return union_desc; +		} + +		buflen -= union_desc->bLength; +		buf += union_desc->bLength; +	} + +	dev_err(&intf->dev, "Missing CDC union descriptor\n"); +	return NULL; +} + +static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pcu) +{ +	const struct usb_cdc_union_desc *union_desc; +	struct usb_host_interface *alt; + +	union_desc = ims_pcu_get_cdc_union_desc(intf); +	if (!union_desc) +		return -EINVAL; + +	pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev, +					 union_desc->bMasterInterface0); + +	alt = pcu->ctrl_intf->cur_altsetting; +	pcu->ep_ctrl = &alt->endpoint[0].desc; +	pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl); + +	pcu->data_intf = usb_ifnum_to_if(pcu->udev, +					 union_desc->bSlaveInterface0); + +	alt = pcu->data_intf->cur_altsetting; +	if (alt->desc.bNumEndpoints != 2) { +		dev_err(pcu->dev, +			"Incorrect number of endpoints on data interface (%d)\n", +			alt->desc.bNumEndpoints); +		return -EINVAL; +	} + +	pcu->ep_out = &alt->endpoint[0].desc; +	if (!usb_endpoint_is_bulk_out(pcu->ep_out)) { +		dev_err(pcu->dev, +			"First endpoint on data interface is not BULK OUT\n"); +		return -EINVAL; +	} + +	pcu->max_out_size = usb_endpoint_maxp(pcu->ep_out); +	if (pcu->max_out_size < 8) { +		dev_err(pcu->dev, +			"Max OUT packet size is too small (%zd)\n", +			pcu->max_out_size); +		return -EINVAL; +	} + +	pcu->ep_in = &alt->endpoint[1].desc; +	if (!usb_endpoint_is_bulk_in(pcu->ep_in)) { +		dev_err(pcu->dev, +			"Second endpoint on data interface is not BULK IN\n"); +		return -EINVAL; +	} + +	pcu->max_in_size = usb_endpoint_maxp(pcu->ep_in); +	if (pcu->max_in_size < 8) { +		dev_err(pcu->dev, +			"Max IN packet size is too small (%zd)\n", +			pcu->max_in_size); +		return -EINVAL; +	} + +	return 0; +} + +static int ims_pcu_start_io(struct ims_pcu *pcu) +{ +	int error; + +	error = usb_submit_urb(pcu->urb_ctrl, GFP_KERNEL); +	if (error) { +		dev_err(pcu->dev, +			"Failed to start control IO - usb_submit_urb failed with result: %d\n", +			error); +		return -EIO; +	} + +	error = usb_submit_urb(pcu->urb_in, GFP_KERNEL); +	if (error) { +		dev_err(pcu->dev, +			"Failed to start IO - usb_submit_urb failed with result: %d\n", +			error); +		usb_kill_urb(pcu->urb_ctrl); +		return -EIO; +	} + +	return 0; +} + +static void ims_pcu_stop_io(struct ims_pcu *pcu) +{ +	usb_kill_urb(pcu->urb_in); +	usb_kill_urb(pcu->urb_ctrl); +} + +static int ims_pcu_line_setup(struct ims_pcu *pcu) +{ +	struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting; +	struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf; +	int error; + +	memset(line, 0, sizeof(*line)); +	line->dwDTERate = cpu_to_le32(57600); +	line->bDataBits = 8; + +	error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), +				USB_CDC_REQ_SET_LINE_CODING, +				USB_TYPE_CLASS | USB_RECIP_INTERFACE, +				0, interface->desc.bInterfaceNumber, +				line, sizeof(struct usb_cdc_line_coding), +				5000); +	if (error < 0) { +		dev_err(pcu->dev, "Failed to set line coding, error: %d\n", +			error); +		return error; +	} + +	error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), +				USB_CDC_REQ_SET_CONTROL_LINE_STATE, +				USB_TYPE_CLASS | USB_RECIP_INTERFACE, +				0x03, interface->desc.bInterfaceNumber, +				NULL, 0, 5000); +	if (error < 0) { +		dev_err(pcu->dev, "Failed to set line state, error: %d\n", +			error); +		return error; +	} + +	return 0; +} + +static int ims_pcu_get_device_info(struct ims_pcu *pcu) +{ +	int error; + +	error = ims_pcu_get_info(pcu); +	if (error) +		return error; + +	error = ims_pcu_execute_query(pcu, GET_FW_VERSION); +	if (error) { +		dev_err(pcu->dev, +			"GET_FW_VERSION command failed, error: %d\n", error); +		return error; +	} + +	snprintf(pcu->fw_version, sizeof(pcu->fw_version), +		 "%02d%02d%02d%02d.%c%c", +		 pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], +		 pcu->cmd_buf[6], pcu->cmd_buf[7]); + +	error = ims_pcu_execute_query(pcu, GET_BL_VERSION); +	if (error) { +		dev_err(pcu->dev, +			"GET_BL_VERSION command failed, error: %d\n", error); +		return error; +	} + +	snprintf(pcu->bl_version, sizeof(pcu->bl_version), +		 "%02d%02d%02d%02d.%c%c", +		 pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], +		 pcu->cmd_buf[6], pcu->cmd_buf[7]); + +	error = ims_pcu_execute_query(pcu, RESET_REASON); +	if (error) { +		dev_err(pcu->dev, +			"RESET_REASON command failed, error: %d\n", error); +		return error; +	} + +	snprintf(pcu->reset_reason, sizeof(pcu->reset_reason), +		 "%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); + +	dev_dbg(pcu->dev, +		"P/N: %s, MD: %s, S/N: %s, FW: %s, BL: %s, RR: %s\n", +		pcu->part_number, +		pcu->date_of_manufacturing, +		pcu->serial_number, +		pcu->fw_version, +		pcu->bl_version, +		pcu->reset_reason); + +	return 0; +} + +static int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id) +{ +	int error; + +	error = ims_pcu_execute_query(pcu, GET_DEVICE_ID); +	if (error) { +		dev_err(pcu->dev, +			"GET_DEVICE_ID command failed, error: %d\n", error); +		return error; +	} + +	*device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET]; +	dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id); + +	return 0; +} + +static int ims_pcu_init_application_mode(struct ims_pcu *pcu) +{ +	static atomic_t device_no = ATOMIC_INIT(0); + +	const struct ims_pcu_device_info *info; +	u8 device_id; +	int error; + +	error = ims_pcu_get_device_info(pcu); +	if (error) { +		/* Device does not respond to basic queries, hopeless */ +		return error; +	} + +	error = ims_pcu_identify_type(pcu, &device_id); +	if (error) { +		dev_err(pcu->dev, +			"Failed to identify device, error: %d\n", error); +		/* +		 * Do not signal error, but do not create input nor +		 * backlight devices either, let userspace figure this +		 * out (flash a new firmware?). +		 */ +		return 0; +	} + +	if (device_id >= ARRAY_SIZE(ims_pcu_device_info) || +	    !ims_pcu_device_info[device_id].keymap) { +		dev_err(pcu->dev, "Device ID %d is not valid\n", device_id); +		/* Same as above, punt to userspace */ +		return 0; +	} + +	/* Device appears to be operable, complete initialization */ +	pcu->device_no = atomic_inc_return(&device_no) - 1; + +	error = ims_pcu_setup_backlight(pcu); +	if (error) +		return error; + +	info = &ims_pcu_device_info[device_id]; +	error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len); +	if (error) +		goto err_destroy_backlight; + +	if (info->has_gamepad) { +		error = ims_pcu_setup_gamepad(pcu); +		if (error) +			goto err_destroy_buttons; +	} + +	pcu->setup_complete = true; + +	return 0; + +err_destroy_backlight: +	ims_pcu_destroy_backlight(pcu); +err_destroy_buttons: +	ims_pcu_destroy_buttons(pcu); +	return error; +} + +static void ims_pcu_destroy_application_mode(struct ims_pcu *pcu) +{ +	if (pcu->setup_complete) { +		pcu->setup_complete = false; +		mb(); /* make sure flag setting is not reordered */ + +		if (pcu->gamepad) +			ims_pcu_destroy_gamepad(pcu); +		ims_pcu_destroy_buttons(pcu); +		ims_pcu_destroy_backlight(pcu); +	} +} + +static int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu) +{ +	int error; + +	error = ims_pcu_execute_bl_command(pcu, QUERY_DEVICE, NULL, 0, +					   IMS_PCU_CMD_RESPONSE_TIMEOUT); +	if (error) { +		dev_err(pcu->dev, "Bootloader does not respond, aborting\n"); +		return error; +	} + +	pcu->fw_start_addr = +		get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]); +	pcu->fw_end_addr = +		get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 15]); + +	dev_info(pcu->dev, +		 "Device is in bootloader mode (addr 0x%08x-0x%08x), requesting firmware\n", +		 pcu->fw_start_addr, pcu->fw_end_addr); + +	error = request_firmware_nowait(THIS_MODULE, true, +					IMS_PCU_FIRMWARE_NAME, +					pcu->dev, GFP_KERNEL, pcu, +					ims_pcu_process_async_firmware); +	if (error) { +		/* This error is not fatal, let userspace have another chance */ +		complete(&pcu->async_firmware_done); +	} + +	return 0; +} + +static void ims_pcu_destroy_bootloader_mode(struct ims_pcu *pcu) +{ +	/* Make sure our initial firmware request has completed */ +	wait_for_completion(&pcu->async_firmware_done); +} + +#define IMS_PCU_APPLICATION_MODE	0 +#define IMS_PCU_BOOTLOADER_MODE		1 + +static struct usb_driver ims_pcu_driver; + +static int ims_pcu_probe(struct usb_interface *intf, +			 const struct usb_device_id *id) +{ +	struct usb_device *udev = interface_to_usbdev(intf); +	struct ims_pcu *pcu; +	int error; + +	pcu = kzalloc(sizeof(struct ims_pcu), GFP_KERNEL); +	if (!pcu) +		return -ENOMEM; + +	pcu->dev = &intf->dev; +	pcu->udev = udev; +	pcu->bootloader_mode = id->driver_info == IMS_PCU_BOOTLOADER_MODE; +	mutex_init(&pcu->cmd_mutex); +	init_completion(&pcu->cmd_done); +	init_completion(&pcu->async_firmware_done); + +	error = ims_pcu_parse_cdc_data(intf, pcu); +	if (error) +		goto err_free_mem; + +	error = usb_driver_claim_interface(&ims_pcu_driver, +					   pcu->data_intf, pcu); +	if (error) { +		dev_err(&intf->dev, +			"Unable to claim corresponding data interface: %d\n", +			error); +		goto err_free_mem; +	} + +	usb_set_intfdata(pcu->ctrl_intf, pcu); +	usb_set_intfdata(pcu->data_intf, pcu); + +	error = ims_pcu_buffers_alloc(pcu); +	if (error) +		goto err_unclaim_intf; + +	error = ims_pcu_start_io(pcu); +	if (error) +		goto err_free_buffers; + +	error = ims_pcu_line_setup(pcu); +	if (error) +		goto err_stop_io; + +	error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group); +	if (error) +		goto err_stop_io; + +	error = pcu->bootloader_mode ? +			ims_pcu_init_bootloader_mode(pcu) : +			ims_pcu_init_application_mode(pcu); +	if (error) +		goto err_remove_sysfs; + +	return 0; + +err_remove_sysfs: +	sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); +err_stop_io: +	ims_pcu_stop_io(pcu); +err_free_buffers: +	ims_pcu_buffers_free(pcu); +err_unclaim_intf: +	usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf); +err_free_mem: +	kfree(pcu); +	return error; +} + +static void ims_pcu_disconnect(struct usb_interface *intf) +{ +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	struct usb_host_interface *alt = intf->cur_altsetting; + +	usb_set_intfdata(intf, NULL); + +	/* +	 * See if we are dealing with control or data interface. The cleanup +	 * happens when we unbind primary (control) interface. +	 */ +	if (alt->desc.bInterfaceClass != USB_CLASS_COMM) +		return; + +	sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); + +	ims_pcu_stop_io(pcu); + +	if (pcu->bootloader_mode) +		ims_pcu_destroy_bootloader_mode(pcu); +	else +		ims_pcu_destroy_application_mode(pcu); + +	ims_pcu_buffers_free(pcu); +	kfree(pcu); +} + +#ifdef CONFIG_PM +static int ims_pcu_suspend(struct usb_interface *intf, +			   pm_message_t message) +{ +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	struct usb_host_interface *alt = intf->cur_altsetting; + +	if (alt->desc.bInterfaceClass == USB_CLASS_COMM) +		ims_pcu_stop_io(pcu); + +	return 0; +} + +static int ims_pcu_resume(struct usb_interface *intf) +{ +	struct ims_pcu *pcu = usb_get_intfdata(intf); +	struct usb_host_interface *alt = intf->cur_altsetting; +	int retval = 0; + +	if (alt->desc.bInterfaceClass == USB_CLASS_COMM) { +		retval = ims_pcu_start_io(pcu); +		if (retval == 0) +			retval = ims_pcu_line_setup(pcu); +	} + +	return retval; +} +#endif + +static const struct usb_device_id ims_pcu_id_table[] = { +	{ +		USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0082, +					USB_CLASS_COMM, +					USB_CDC_SUBCLASS_ACM, +					USB_CDC_ACM_PROTO_AT_V25TER), +		.driver_info = IMS_PCU_APPLICATION_MODE, +	}, +	{ +		USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0083, +					USB_CLASS_COMM, +					USB_CDC_SUBCLASS_ACM, +					USB_CDC_ACM_PROTO_AT_V25TER), +		.driver_info = IMS_PCU_BOOTLOADER_MODE, +	}, +	{ } +}; + +static struct usb_driver ims_pcu_driver = { +	.name			= "ims_pcu", +	.id_table		= ims_pcu_id_table, +	.probe			= ims_pcu_probe, +	.disconnect		= ims_pcu_disconnect, +#ifdef CONFIG_PM +	.suspend		= ims_pcu_suspend, +	.resume			= ims_pcu_resume, +	.reset_resume		= ims_pcu_resume, +#endif +}; + +module_usb_driver(ims_pcu_driver); + +MODULE_DESCRIPTION("IMS Passenger Control Unit driver"); +MODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/mma8450.c b/drivers/input/misc/mma8450.c index 480557f14f2..f3309696d05 100644 --- a/drivers/input/misc/mma8450.c +++ b/drivers/input/misc/mma8450.c @@ -123,9 +123,9 @@ static void mma8450_poll(struct input_polled_dev *dev)  	if (ret < 0)  		return; -	x = ((buf[1] << 4) & 0xff0) | (buf[0] & 0xf); -	y = ((buf[3] << 4) & 0xff0) | (buf[2] & 0xf); -	z = ((buf[5] << 4) & 0xff0) | (buf[4] & 0xf); +	x = ((int)(s8)buf[1] << 4) | (buf[0] & 0xf); +	y = ((int)(s8)buf[3] << 4) | (buf[2] & 0xf); +	z = ((int)(s8)buf[5] << 4) | (buf[4] & 0xf);  	input_report_abs(dev->input, ABS_X, x);  	input_report_abs(dev->input, ABS_Y, y); diff --git a/drivers/input/misc/twl4030-pwrbutton.c b/drivers/input/misc/twl4030-pwrbutton.c index 27c2bc8aa89..b9a05fda03e 100644 --- a/drivers/input/misc/twl4030-pwrbutton.c +++ b/drivers/input/misc/twl4030-pwrbutton.c @@ -114,18 +114,8 @@ static struct platform_driver twl4030_pwrbutton_driver = {  	},  }; -static int __init twl4030_pwrbutton_init(void) -{ -	return platform_driver_probe(&twl4030_pwrbutton_driver, +module_platform_driver_probe(twl4030_pwrbutton_driver,  			twl4030_pwrbutton_probe); -} -module_init(twl4030_pwrbutton_init); - -static void __exit twl4030_pwrbutton_exit(void) -{ -	platform_driver_unregister(&twl4030_pwrbutton_driver); -} -module_exit(twl4030_pwrbutton_exit);  MODULE_ALIAS("platform:twl4030_pwrbutton");  MODULE_DESCRIPTION("Triton2 Power Button"); diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index 0238e0e1433..7c5d72a6a26 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -1013,8 +1013,8 @@ static int alps_rpt_cmd(struct psmouse *psmouse, int init_command,  	if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))  		return -EIO; -	psmouse_dbg(psmouse, "%2.2X report: %2.2x %2.2x %2.2x\n", -		    repeated_command, param[0], param[1], param[2]); +	psmouse_dbg(psmouse, "%2.2X report: %3ph\n", +		    repeated_command, param);  	return 0;  } @@ -1274,9 +1274,7 @@ static int alps_setup_trackstick_v3(struct psmouse *psmouse, int reg_base)  		psmouse_warn(psmouse, "trackstick E7 report failed\n");  		ret = -ENODEV;  	} else { -		psmouse_dbg(psmouse, -			    "trackstick E7 report: %2.2x %2.2x %2.2x\n", -			    param[0], param[1], param[2]); +		psmouse_dbg(psmouse, "trackstick E7 report: %3ph\n", param);  		/*  		 * Not sure what this does, but it is absolutely @@ -1323,6 +1321,7 @@ static int alps_hw_init_v3(struct psmouse *psmouse)  	reg_val = alps_probe_trackstick_v3(psmouse, ALPS_REG_BASE_PINNACLE);  	if (reg_val == -EIO)  		goto error; +  	if (reg_val == 0 &&  	    alps_setup_trackstick_v3(psmouse, ALPS_REG_BASE_PINNACLE) == -EIO)  		goto error; @@ -1676,8 +1675,7 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)  	}  	psmouse_info(psmouse, -		"Unknown ALPS touchpad: E7=%2.2x %2.2x %2.2x, EC=%2.2x %2.2x %2.2x\n", -		e7[0], e7[1], e7[2], ec[0], ec[1], ec[2]); +		     "Unknown ALPS touchpad: E7=%3ph, EC=%3ph\n", e7, ec);  	return -EINVAL;  } diff --git a/drivers/input/mouse/amimouse.c b/drivers/input/mouse/amimouse.c index 5fa99341a39..b55d5af217a 100644 --- a/drivers/input/mouse/amimouse.c +++ b/drivers/input/mouse/amimouse.c @@ -146,18 +146,6 @@ static struct platform_driver amimouse_driver = {  	},  }; -static int __init amimouse_init(void) -{ -	return platform_driver_probe(&amimouse_driver, amimouse_probe); -} - -module_init(amimouse_init); - -static void __exit amimouse_exit(void) -{ -	platform_driver_unregister(&amimouse_driver); -} - -module_exit(amimouse_exit); +module_platform_driver_probe(amimouse_driver, amimouse_probe);  MODULE_ALIAS("platform:amiga-mouse"); diff --git a/drivers/input/mouse/trackpoint.c b/drivers/input/mouse/trackpoint.c index f3102494237..ca843b6cf6b 100644 --- a/drivers/input/mouse/trackpoint.c +++ b/drivers/input/mouse/trackpoint.c @@ -20,9 +20,34 @@  #include "trackpoint.h"  /* + * Power-on Reset: Resets all trackpoint parameters, including RAM values, + * to defaults. + * Returns zero on success, non-zero on failure. + */ +static int trackpoint_power_on_reset(struct ps2dev *ps2dev) +{ +	unsigned char results[2]; +	int tries = 0; + +	/* Issue POR command, and repeat up to once if 0xFC00 received */ +	do { +		if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) || +		    ps2_command(ps2dev, results, MAKE_PS2_CMD(0, 2, TP_POR))) +			return -1; +	} while (results[0] == 0xFC && results[1] == 0x00 && ++tries < 2); + +	/* Check for success response -- 0xAA00 */ +	if (results[0] != 0xAA || results[1] != 0x00) +		return -1; + +	return 0; +} + +/*   * Device IO: read, write and toggle bit   */ -static int trackpoint_read(struct ps2dev *ps2dev, unsigned char loc, unsigned char *results) +static int trackpoint_read(struct ps2dev *ps2dev, +			   unsigned char loc, unsigned char *results)  {  	if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) ||  	    ps2_command(ps2dev, results, MAKE_PS2_CMD(0, 1, loc))) { @@ -32,7 +57,8 @@ static int trackpoint_read(struct ps2dev *ps2dev, unsigned char loc, unsigned ch  	return 0;  } -static int trackpoint_write(struct ps2dev *ps2dev, unsigned char loc, unsigned char val) +static int trackpoint_write(struct ps2dev *ps2dev, +			    unsigned char loc, unsigned char val)  {  	if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) ||  	    ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_WRITE_MEM)) || @@ -44,7 +70,8 @@ static int trackpoint_write(struct ps2dev *ps2dev, unsigned char loc, unsigned c  	return 0;  } -static int trackpoint_toggle_bit(struct ps2dev *ps2dev, unsigned char loc, unsigned char mask) +static int trackpoint_toggle_bit(struct ps2dev *ps2dev, +				 unsigned char loc, unsigned char mask)  {  	/* Bad things will happen if the loc param isn't in this range */  	if (loc < 0x20 || loc >= 0x2F) @@ -60,6 +87,18 @@ static int trackpoint_toggle_bit(struct ps2dev *ps2dev, unsigned char loc, unsig  	return 0;  } +static int trackpoint_update_bit(struct ps2dev *ps2dev, unsigned char loc, +				 unsigned char mask, unsigned char value) +{ +	int retval = 0; +	unsigned char data; + +	trackpoint_read(ps2dev, loc, &data); +	if (((data & mask) == mask) != !!value) +		retval = trackpoint_toggle_bit(ps2dev, loc, mask); + +	return retval; +}  /*   * Trackpoint-specific attributes @@ -69,6 +108,7 @@ struct trackpoint_attr_data {  	unsigned char command;  	unsigned char mask;  	unsigned char inverted; +	unsigned char power_on_default;  };  static ssize_t trackpoint_show_int_attr(struct psmouse *psmouse, void *data, char *buf) @@ -102,10 +142,11 @@ static ssize_t trackpoint_set_int_attr(struct psmouse *psmouse, void *data,  	return count;  } -#define TRACKPOINT_INT_ATTR(_name, _command)					\ +#define TRACKPOINT_INT_ATTR(_name, _command, _default)				\  	static struct trackpoint_attr_data trackpoint_attr_##_name = {		\  		.field_offset = offsetof(struct trackpoint_data, _name),	\  		.command = _command,						\ +		.power_on_default = _default,					\  	};									\  	PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO,				\  			    &trackpoint_attr_##_name,				\ @@ -139,31 +180,60 @@ static ssize_t trackpoint_set_bit_attr(struct psmouse *psmouse, void *data,  } -#define TRACKPOINT_BIT_ATTR(_name, _command, _mask, _inv)				\ -	static struct trackpoint_attr_data trackpoint_attr_##_name = {		\ -		.field_offset	= offsetof(struct trackpoint_data, _name),	\ -		.command	= _command,					\ -		.mask		= _mask,					\ -		.inverted	= _inv,						\ -	};									\ -	PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO,				\ -			    &trackpoint_attr_##_name,				\ -			    trackpoint_show_int_attr, trackpoint_set_bit_attr) +#define TRACKPOINT_BIT_ATTR(_name, _command, _mask, _inv, _default)	\ +static struct trackpoint_attr_data trackpoint_attr_##_name = {		\ +	.field_offset		= offsetof(struct trackpoint_data,	\ +					   _name),			\ +	.command		= _command,				\ +	.mask			= _mask,				\ +	.inverted		= _inv,					\ +	.power_on_default	= _default,				\ +	};								\ +PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO,				\ +		    &trackpoint_attr_##_name,				\ +		    trackpoint_show_int_attr, trackpoint_set_bit_attr) -TRACKPOINT_INT_ATTR(sensitivity, TP_SENS); -TRACKPOINT_INT_ATTR(speed, TP_SPEED); -TRACKPOINT_INT_ATTR(inertia, TP_INERTIA); -TRACKPOINT_INT_ATTR(reach, TP_REACH); -TRACKPOINT_INT_ATTR(draghys, TP_DRAGHYS); -TRACKPOINT_INT_ATTR(mindrag, TP_MINDRAG); -TRACKPOINT_INT_ATTR(thresh, TP_THRESH); -TRACKPOINT_INT_ATTR(upthresh, TP_UP_THRESH); -TRACKPOINT_INT_ATTR(ztime, TP_Z_TIME); -TRACKPOINT_INT_ATTR(jenks, TP_JENKS_CURV); +#define TRACKPOINT_UPDATE_BIT(_psmouse, _tp, _name)			\ +do {									\ +	struct trackpoint_attr_data *_attr = &trackpoint_attr_##_name;	\ +									\ +	trackpoint_update_bit(&_psmouse->ps2dev,			\ +			_attr->command, _attr->mask, _tp->_name);	\ +} while (0) -TRACKPOINT_BIT_ATTR(press_to_select, TP_TOGGLE_PTSON, TP_MASK_PTSON, 0); -TRACKPOINT_BIT_ATTR(skipback, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK, 0); -TRACKPOINT_BIT_ATTR(ext_dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV, 1); +#define TRACKPOINT_UPDATE(_power_on, _psmouse, _tp, _name)		\ +do {									\ +	if (!_power_on ||						\ +	    _tp->_name != trackpoint_attr_##_name.power_on_default) {	\ +		if (!trackpoint_attr_##_name.mask)			\ +			trackpoint_write(&_psmouse->ps2dev,		\ +				 trackpoint_attr_##_name.command,	\ +				 _tp->_name);				\ +		else							\ +			TRACKPOINT_UPDATE_BIT(_psmouse, _tp, _name);	\ +	}								\ +} while (0) + +#define TRACKPOINT_SET_POWER_ON_DEFAULT(_tp, _name)				\ +	(_tp->_name = trackpoint_attr_##_name.power_on_default) + +TRACKPOINT_INT_ATTR(sensitivity, TP_SENS, TP_DEF_SENS); +TRACKPOINT_INT_ATTR(speed, TP_SPEED, TP_DEF_SPEED); +TRACKPOINT_INT_ATTR(inertia, TP_INERTIA, TP_DEF_INERTIA); +TRACKPOINT_INT_ATTR(reach, TP_REACH, TP_DEF_REACH); +TRACKPOINT_INT_ATTR(draghys, TP_DRAGHYS, TP_DEF_DRAGHYS); +TRACKPOINT_INT_ATTR(mindrag, TP_MINDRAG, TP_DEF_MINDRAG); +TRACKPOINT_INT_ATTR(thresh, TP_THRESH, TP_DEF_THRESH); +TRACKPOINT_INT_ATTR(upthresh, TP_UP_THRESH, TP_DEF_UP_THRESH); +TRACKPOINT_INT_ATTR(ztime, TP_Z_TIME, TP_DEF_Z_TIME); +TRACKPOINT_INT_ATTR(jenks, TP_JENKS_CURV, TP_DEF_JENKS_CURV); + +TRACKPOINT_BIT_ATTR(press_to_select, TP_TOGGLE_PTSON, TP_MASK_PTSON, 0, +		    TP_DEF_PTSON); +TRACKPOINT_BIT_ATTR(skipback, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK, 0, +		    TP_DEF_SKIPBACK); +TRACKPOINT_BIT_ATTR(ext_dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV, 1, +		    TP_DEF_EXT_DEV);  static struct attribute *trackpoint_attrs[] = {  	&psmouse_attr_sensitivity.dattr.attr, @@ -202,73 +272,72 @@ static int trackpoint_start_protocol(struct psmouse *psmouse, unsigned char *fir  	return 0;  } -static int trackpoint_sync(struct psmouse *psmouse) +/* + * Write parameters to trackpad. + * in_power_on_state: Set to true if TP is in default / power-on state (ex. if + *		      power-on reset was run). If so, values will only be + *		      written to TP if they differ from power-on default. + */ +static int trackpoint_sync(struct psmouse *psmouse, bool in_power_on_state)  {  	struct trackpoint_data *tp = psmouse->private; -	unsigned char toggle; - -	/* Disable features that may make device unusable with this driver */ -	trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_TWOHAND, &toggle); -	if (toggle & TP_MASK_TWOHAND) -		trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_TWOHAND, TP_MASK_TWOHAND); - -	trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG, &toggle); -	if (toggle & TP_MASK_SOURCE_TAG) -		trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG, TP_MASK_SOURCE_TAG); - -	trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_MB, &toggle); -	if (toggle & TP_MASK_MB) -		trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_MB, TP_MASK_MB); - -	/* Push the config to the device */ -	trackpoint_write(&psmouse->ps2dev, TP_SENS, tp->sensitivity); -	trackpoint_write(&psmouse->ps2dev, TP_INERTIA, tp->inertia); -	trackpoint_write(&psmouse->ps2dev, TP_SPEED, tp->speed); -	trackpoint_write(&psmouse->ps2dev, TP_REACH, tp->reach); -	trackpoint_write(&psmouse->ps2dev, TP_DRAGHYS, tp->draghys); -	trackpoint_write(&psmouse->ps2dev, TP_MINDRAG, tp->mindrag); +	if (!in_power_on_state) { +		/* +		 * Disable features that may make device unusable +		 * with this driver. +		 */ +		trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_TWOHAND, +				      TP_MASK_TWOHAND, TP_DEF_TWOHAND); -	trackpoint_write(&psmouse->ps2dev, TP_THRESH, tp->thresh); -	trackpoint_write(&psmouse->ps2dev, TP_UP_THRESH, tp->upthresh); +		trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG, +				      TP_MASK_SOURCE_TAG, TP_DEF_SOURCE_TAG); -	trackpoint_write(&psmouse->ps2dev, TP_Z_TIME, tp->ztime); -	trackpoint_write(&psmouse->ps2dev, TP_JENKS_CURV, tp->jenks); - -	trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_PTSON, &toggle); -	if (((toggle & TP_MASK_PTSON) == TP_MASK_PTSON) != tp->press_to_select) -		 trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_PTSON, TP_MASK_PTSON); +		trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_MB, +				      TP_MASK_MB, TP_DEF_MB); +	} -	trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_SKIPBACK, &toggle); -	if (((toggle & TP_MASK_SKIPBACK) == TP_MASK_SKIPBACK) != tp->skipback) -		trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK); +	/* +	 * These properties can be changed in this driver. Only +	 * configure them if the values are non-default or if the TP is in +	 * an unknown state. +	 */ +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, sensitivity); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, inertia); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, speed); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, reach); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, draghys); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, mindrag); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, thresh); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, upthresh); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ztime); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, jenks); -	trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_EXT_DEV, &toggle); -	if (((toggle & TP_MASK_EXT_DEV) == TP_MASK_EXT_DEV) != tp->ext_dev) -		trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV); +	/* toggles */ +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, press_to_select); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, skipback); +	TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ext_dev);  	return 0;  }  static void trackpoint_defaults(struct trackpoint_data *tp)  { -	tp->press_to_select = TP_DEF_PTSON; -	tp->sensitivity = TP_DEF_SENS; -	tp->speed = TP_DEF_SPEED; -	tp->reach = TP_DEF_REACH; - -	tp->draghys = TP_DEF_DRAGHYS; -	tp->mindrag = TP_DEF_MINDRAG; - -	tp->thresh = TP_DEF_THRESH; -	tp->upthresh = TP_DEF_UP_THRESH; +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, sensitivity); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, speed); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, reach); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, draghys); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, mindrag); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, thresh); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, upthresh); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ztime); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, jenks); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, inertia); -	tp->ztime = TP_DEF_Z_TIME; -	tp->jenks = TP_DEF_JENKS_CURV; - -	tp->inertia = TP_DEF_INERTIA; -	tp->skipback = TP_DEF_SKIPBACK; -	tp->ext_dev = TP_DEF_EXT_DEV; +	/* toggles */ +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, press_to_select); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, skipback); +	TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ext_dev);  }  static void trackpoint_disconnect(struct psmouse *psmouse) @@ -281,10 +350,13 @@ static void trackpoint_disconnect(struct psmouse *psmouse)  static int trackpoint_reconnect(struct psmouse *psmouse)  { +	int reset_fail; +  	if (trackpoint_start_protocol(psmouse, NULL))  		return -1; -	if (trackpoint_sync(psmouse)) +	reset_fail = trackpoint_power_on_reset(&psmouse->ps2dev); +	if (trackpoint_sync(psmouse, !reset_fail))  		return -1;  	return 0; @@ -322,7 +394,12 @@ int trackpoint_detect(struct psmouse *psmouse, bool set_properties)  		__set_bit(BTN_MIDDLE, psmouse->dev->keybit);  	trackpoint_defaults(psmouse->private); -	trackpoint_sync(psmouse); + +	error = trackpoint_power_on_reset(&psmouse->ps2dev); + +	/* Write defaults to TP only if reset fails. */ +	if (error) +		trackpoint_sync(psmouse, false);  	error = sysfs_create_group(&ps2dev->serio->dev.kobj, &trackpoint_attr_group);  	if (error) { diff --git a/drivers/input/mouse/trackpoint.h b/drivers/input/mouse/trackpoint.h index e558a709661..ecd0547964a 100644 --- a/drivers/input/mouse/trackpoint.h +++ b/drivers/input/mouse/trackpoint.h @@ -126,6 +126,8 @@  #define TP_DEF_PTSON		0x00  #define TP_DEF_SKIPBACK		0x00  #define TP_DEF_EXT_DEV		0x00	/* 0 means enabled */ +#define TP_DEF_TWOHAND		0x00 +#define TP_DEF_SOURCE_TAG	0x00  #define MAKE_PS2_CMD(params, results, cmd) ((params<<12) | (results<<8) | (cmd)) @@ -136,9 +138,9 @@ struct trackpoint_data  	unsigned char thresh, upthresh;  	unsigned char ztime, jenks; +	/* toggles */  	unsigned char press_to_select;  	unsigned char skipback; -  	unsigned char ext_dev;  }; diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index 3ec5ef2dd44..aebfe3ecb94 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -245,4 +245,14 @@ config SERIO_ARC_PS2  	  To compile this driver as a module, choose M here; the module  	  will be called arc_ps2. +config SERIO_APBPS2 +	tristate "GRLIB APBPS2 PS/2 keyboard/mouse controller" +	depends on OF +	help +	  Say Y here if you want support for GRLIB APBPS2 peripherals used +	  to connect to PS/2 keyboard and/or mouse. + +	  To compile this driver as a module, choose M here: the module will +	  be called apbps2. +  endif diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile index 4b0c8f84f1c..8edb36c2cdb 100644 --- a/drivers/input/serio/Makefile +++ b/drivers/input/serio/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_SERIO_AMS_DELTA)	+= ams_delta_serio.o  obj-$(CONFIG_SERIO_XILINX_XPS_PS2)	+= xilinx_ps2.o  obj-$(CONFIG_SERIO_ALTERA_PS2)	+= altera_ps2.o  obj-$(CONFIG_SERIO_ARC_PS2)	+= arc_ps2.o +obj-$(CONFIG_SERIO_APBPS2)	+= apbps2.o diff --git a/drivers/input/serio/apbps2.c b/drivers/input/serio/apbps2.c new file mode 100644 index 00000000000..17e01a807dd --- /dev/null +++ b/drivers/input/serio/apbps2.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2013 Aeroflex Gaisler + * + * This driver supports the APBPS2 PS/2 core available in the GRLIB + * VHDL IP core library. + * + * Full documentation of the APBPS2 core can be found here: + * http://www.gaisler.com/products/grlib/grip.pdf + * + * See "Documentation/devicetree/bindings/input/ps2keyb-mouse-apbps2.txt" for + * information on open firmware properties. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Contributors: Daniel Hellstrom <daniel@gaisler.com> + */ +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/of_irq.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/io.h> + +struct apbps2_regs { +	u32 __iomem data;	/* 0x00 */ +	u32 __iomem status;	/* 0x04 */ +	u32 __iomem ctrl;	/* 0x08 */ +	u32 __iomem reload;	/* 0x0c */ +}; + +#define APBPS2_STATUS_DR	(1<<0) +#define APBPS2_STATUS_PE	(1<<1) +#define APBPS2_STATUS_FE	(1<<2) +#define APBPS2_STATUS_KI	(1<<3) +#define APBPS2_STATUS_RF	(1<<4) +#define APBPS2_STATUS_TF	(1<<5) +#define APBPS2_STATUS_TCNT	(0x1f<<22) +#define APBPS2_STATUS_RCNT	(0x1f<<27) + +#define APBPS2_CTRL_RE		(1<<0) +#define APBPS2_CTRL_TE		(1<<1) +#define APBPS2_CTRL_RI		(1<<2) +#define APBPS2_CTRL_TI		(1<<3) + +struct apbps2_priv { +	struct serio		*io; +	struct apbps2_regs	*regs; +}; + +static int apbps2_idx; + +static irqreturn_t apbps2_isr(int irq, void *dev_id) +{ +	struct apbps2_priv *priv = dev_id; +	unsigned long status, data, rxflags; +	irqreturn_t ret = IRQ_NONE; + +	while ((status = ioread32be(&priv->regs->status)) & APBPS2_STATUS_DR) { +		data = ioread32be(&priv->regs->data); +		rxflags = (status & APBPS2_STATUS_PE) ? SERIO_PARITY : 0; +		rxflags |= (status & APBPS2_STATUS_FE) ? SERIO_FRAME : 0; + +		/* clear error bits? */ +		if (rxflags) +			iowrite32be(0, &priv->regs->status); + +		serio_interrupt(priv->io, data, rxflags); + +		ret = IRQ_HANDLED; +	} + +	return ret; +} + +static int apbps2_write(struct serio *io, unsigned char val) +{ +	struct apbps2_priv *priv = io->port_data; +	unsigned int tleft = 10000; /* timeout in 100ms */ + +	/* delay until PS/2 controller has room for more chars */ +	while ((ioread32be(&priv->regs->status) & APBPS2_STATUS_TF) && tleft--) +		udelay(10); + +	if ((ioread32be(&priv->regs->status) & APBPS2_STATUS_TF) == 0) { +		iowrite32be(val, &priv->regs->data); + +		iowrite32be(APBPS2_CTRL_RE | APBPS2_CTRL_RI | APBPS2_CTRL_TE, +				&priv->regs->ctrl); +		return 0; +	} + +	return -ETIMEDOUT; +} + +static int apbps2_open(struct serio *io) +{ +	struct apbps2_priv *priv = io->port_data; +	int limit; +	unsigned long tmp; + +	/* clear error flags */ +	iowrite32be(0, &priv->regs->status); + +	/* Clear old data if available (unlikely) */ +	limit = 1024; +	while ((ioread32be(&priv->regs->status) & APBPS2_STATUS_DR) && --limit) +		tmp = ioread32be(&priv->regs->data); + +	/* Enable reciever and it's interrupt */ +	iowrite32be(APBPS2_CTRL_RE | APBPS2_CTRL_RI, &priv->regs->ctrl); + +	return 0; +} + +static void apbps2_close(struct serio *io) +{ +	struct apbps2_priv *priv = io->port_data; + +	/* stop interrupts at PS/2 HW level */ +	iowrite32be(0, &priv->regs->ctrl); +} + +/* Initialize one APBPS2 PS/2 core */ +static int apbps2_of_probe(struct platform_device *ofdev) +{ +	struct apbps2_priv *priv; +	int irq, err; +	u32 freq_hz; +	struct resource *res; + +	priv = devm_kzalloc(&ofdev->dev, sizeof(*priv), GFP_KERNEL); +	if (!priv) { +		dev_err(&ofdev->dev, "memory allocation failed\n"); +		return -ENOMEM; +	} + +	/* Find Device Address */ +	res = platform_get_resource(ofdev, IORESOURCE_MEM, 0); +	priv->regs = devm_ioremap_resource(&ofdev->dev, res); +	if (IS_ERR(priv->regs)) +		return PTR_ERR(priv->regs); + +	/* Reset hardware, disable interrupt */ +	iowrite32be(0, &priv->regs->ctrl); + +	/* IRQ */ +	irq = irq_of_parse_and_map(ofdev->dev.of_node, 0); +	err = devm_request_irq(&ofdev->dev, irq, apbps2_isr, +				IRQF_SHARED, "apbps2", priv); +	if (err) { +		dev_err(&ofdev->dev, "request IRQ%d failed\n", irq); +		return err; +	} + +	/* Get core frequency */ +	if (of_property_read_u32(ofdev->dev.of_node, "freq", &freq_hz)) { +		dev_err(&ofdev->dev, "unable to get core frequency\n"); +		return -EINVAL; +	} + +	/* Set reload register to core freq in kHz/10 */ +	iowrite32be(freq_hz / 10000, &priv->regs->reload); + +	priv->io = kzalloc(sizeof(struct serio), GFP_KERNEL); +	if (!priv->io) +		return -ENOMEM; + +	priv->io->id.type = SERIO_8042; +	priv->io->open = apbps2_open; +	priv->io->close = apbps2_close; +	priv->io->write = apbps2_write; +	priv->io->port_data = priv; +	strlcpy(priv->io->name, "APBPS2 PS/2", sizeof(priv->io->name)); +	snprintf(priv->io->phys, sizeof(priv->io->phys), +		 "apbps2_%d", apbps2_idx++); + +	dev_info(&ofdev->dev, "irq = %d, base = 0x%p\n", irq, priv->regs); + +	serio_register_port(priv->io); + +	platform_set_drvdata(ofdev, priv); + +	return 0; +} + +static int apbps2_of_remove(struct platform_device *of_dev) +{ +	struct apbps2_priv *priv = platform_get_drvdata(of_dev); + +	serio_unregister_port(priv->io); + +	return 0; +} + +static struct of_device_id apbps2_of_match[] = { +	{ .name = "GAISLER_APBPS2", }, +	{ .name = "01_060", }, +	{} +}; + +MODULE_DEVICE_TABLE(of, apbps2_of_match); + +static struct platform_driver apbps2_of_driver = { +	.driver = { +		.name = "grlib-apbps2", +		.owner = THIS_MODULE, +		.of_match_table = apbps2_of_match, +	}, +	.probe = apbps2_of_probe, +	.remove = apbps2_of_remove, +}; + +module_platform_driver(apbps2_of_driver); + +MODULE_AUTHOR("Aeroflex Gaisler AB."); +MODULE_DESCRIPTION("GRLIB APBPS2 PS/2 serial I/O"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/arc_ps2.c b/drivers/input/serio/arc_ps2.c index c52e3e589f7..3fb7727c8ea 100644 --- a/drivers/input/serio/arc_ps2.c +++ b/drivers/input/serio/arc_ps2.c @@ -14,6 +14,7 @@  #include <linux/input.h>  #include <linux/serio.h>  #include <linux/platform_device.h> +#include <linux/of.h>  #include <linux/io.h>  #include <linux/kernel.h>  #include <linux/slab.h> @@ -259,10 +260,19 @@ static int arc_ps2_remove(struct platform_device *pdev)  	return 0;  } +#ifdef CONFIG_OF +static const struct of_device_id arc_ps2_match[] = { +	{ .compatible = "snps,arc_ps2" }, +	{ }, +}; +MODULE_DEVICE_TABLE(of, arc_ps2_match); +#endif +  static struct platform_driver arc_ps2_driver = {  	.driver	= { -		.name	= "arc_ps2", -		.owner	= THIS_MODULE, +		.name		= "arc_ps2", +		.owner		= THIS_MODULE, +		.of_match_table	= of_match_ptr(arc_ps2_match),  	},  	.probe	= arc_ps2_probe,  	.remove	= arc_ps2_remove, diff --git a/drivers/input/serio/at32psif.c b/drivers/input/serio/at32psif.c index 36e799c31f5..190ce35af7d 100644 --- a/drivers/input/serio/at32psif.c +++ b/drivers/input/serio/at32psif.c @@ -359,18 +359,7 @@ static struct platform_driver psif_driver = {  	},  }; -static int __init psif_init(void) -{ -	return platform_driver_probe(&psif_driver, psif_probe); -} - -static void __exit psif_exit(void) -{ -	platform_driver_unregister(&psif_driver); -} - -module_init(psif_init); -module_exit(psif_exit); +module_platform_driver_probe(psif_driver, psif_probe);  MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>");  MODULE_DESCRIPTION("Atmel AVR32 PSIF PS/2 driver"); diff --git a/drivers/input/serio/q40kbd.c b/drivers/input/serio/q40kbd.c index 70fe542839f..436a3433f8e 100644 --- a/drivers/input/serio/q40kbd.c +++ b/drivers/input/serio/q40kbd.c @@ -193,15 +193,4 @@ static struct platform_driver q40kbd_driver = {  	.remove		= q40kbd_remove,  }; -static int __init q40kbd_init(void) -{ -	return platform_driver_probe(&q40kbd_driver, q40kbd_probe); -} - -static void __exit q40kbd_exit(void) -{ -	platform_driver_unregister(&q40kbd_driver); -} - -module_init(q40kbd_init); -module_exit(q40kbd_exit); +module_platform_driver_probe(q40kbd_driver, q40kbd_probe); diff --git a/drivers/input/touchscreen/ad7877.c b/drivers/input/touchscreen/ad7877.c index 23fa829b869..f3a174a83c8 100644 --- a/drivers/input/touchscreen/ad7877.c +++ b/drivers/input/touchscreen/ad7877.c @@ -273,7 +273,7 @@ static int ad7877_write(struct spi_device *spi, u16 reg, u16 val)  static int ad7877_read_adc(struct spi_device *spi, unsigned command)  { -	struct ad7877 *ts = dev_get_drvdata(&spi->dev); +	struct ad7877 *ts = spi_get_drvdata(spi);  	struct ser_req *req;  	int status;  	int sample; @@ -720,7 +720,7 @@ static int ad7877_probe(struct spi_device *spi)  		goto err_free_mem;  	} -	dev_set_drvdata(&spi->dev, ts); +	spi_set_drvdata(spi, ts);  	ts->spi = spi;  	ts->input = input_dev; @@ -806,13 +806,13 @@ err_free_irq:  err_free_mem:  	input_free_device(input_dev);  	kfree(ts); -	dev_set_drvdata(&spi->dev, NULL); +	spi_set_drvdata(spi, NULL);  	return err;  }  static int ad7877_remove(struct spi_device *spi)  { -	struct ad7877 *ts = dev_get_drvdata(&spi->dev); +	struct ad7877 *ts = spi_get_drvdata(spi);  	sysfs_remove_group(&spi->dev.kobj, &ad7877_attr_group); @@ -823,7 +823,7 @@ static int ad7877_remove(struct spi_device *spi)  	kfree(ts);  	dev_dbg(&spi->dev, "unregistered touchscreen\n"); -	dev_set_drvdata(&spi->dev, NULL); +	spi_set_drvdata(spi, NULL);  	return 0;  } diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c index 434c3df250c..84ccf140c1b 100644 --- a/drivers/input/touchscreen/ads7846.c +++ b/drivers/input/touchscreen/ads7846.c @@ -1245,7 +1245,7 @@ static int ads7846_probe(struct spi_device *spi)  		goto err_free_mem;  	} -	dev_set_drvdata(&spi->dev, ts); +	spi_set_drvdata(spi, ts);  	ts->packet = packet;  	ts->spi = spi; @@ -1397,7 +1397,7 @@ static int ads7846_probe(struct spi_device *spi)  static int ads7846_remove(struct spi_device *spi)  { -	struct ads7846 *ts = dev_get_drvdata(&spi->dev); +	struct ads7846 *ts = spi_get_drvdata(spi);  	device_init_wakeup(&spi->dev, false); diff --git a/drivers/input/touchscreen/atmel-wm97xx.c b/drivers/input/touchscreen/atmel-wm97xx.c index c5c2dbb9386..2c1e46b7e45 100644 --- a/drivers/input/touchscreen/atmel-wm97xx.c +++ b/drivers/input/touchscreen/atmel-wm97xx.c @@ -432,17 +432,7 @@ static struct platform_driver atmel_wm97xx_driver = {  	},  }; -static int __init atmel_wm97xx_init(void) -{ -	return platform_driver_probe(&atmel_wm97xx_driver, atmel_wm97xx_probe); -} -module_init(atmel_wm97xx_init); - -static void __exit atmel_wm97xx_exit(void) -{ -	platform_driver_unregister(&atmel_wm97xx_driver); -} -module_exit(atmel_wm97xx_exit); +module_platform_driver_probe(atmel_wm97xx_driver, atmel_wm97xx_probe);  MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>");  MODULE_DESCRIPTION("wm97xx continuous touch driver for Atmel AT91 and AVR32"); diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c index c6e19a96348..d3f9f6b0f9b 100644 --- a/drivers/input/touchscreen/auo-pixcir-ts.c +++ b/drivers/input/touchscreen/auo-pixcir-ts.c @@ -31,6 +31,8 @@  #include <linux/delay.h>  #include <linux/gpio.h>  #include <linux/input/auo-pixcir-ts.h> +#include <linux/of.h> +#include <linux/of_gpio.h>  /*   * Coordinate calculation: @@ -111,6 +113,7 @@  struct auo_pixcir_ts {  	struct i2c_client	*client;  	struct input_dev	*input; +	const struct auo_pixcir_ts_platdata *pdata;  	char			phys[32];  	/* special handling for touch_indicate interupt mode */ @@ -132,7 +135,7 @@ static int auo_pixcir_collect_data(struct auo_pixcir_ts *ts,  				   struct auo_point_t *point)  {  	struct i2c_client *client = ts->client; -	const struct auo_pixcir_ts_platdata *pdata = client->dev.platform_data; +	const struct auo_pixcir_ts_platdata *pdata = ts->pdata;  	uint8_t raw_coord[8];  	uint8_t raw_area[4];  	int i, ret; @@ -178,8 +181,7 @@ static int auo_pixcir_collect_data(struct auo_pixcir_ts *ts,  static irqreturn_t auo_pixcir_interrupt(int irq, void *dev_id)  {  	struct auo_pixcir_ts *ts = dev_id; -	struct i2c_client *client = ts->client; -	const struct auo_pixcir_ts_platdata *pdata = client->dev.platform_data; +	const struct auo_pixcir_ts_platdata *pdata = ts->pdata;  	struct auo_point_t point[AUO_PIXCIR_REPORT_POINTS];  	int i;  	int ret; @@ -290,7 +292,7 @@ static int auo_pixcir_int_config(struct auo_pixcir_ts *ts,  					   int int_setting)  {  	struct i2c_client *client = ts->client; -	struct auo_pixcir_ts_platdata *pdata = client->dev.platform_data; +	const struct auo_pixcir_ts_platdata *pdata = ts->pdata;  	int ret;  	ret = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_INT_SETTING); @@ -479,53 +481,105 @@ unlock:  }  #endif -static SIMPLE_DEV_PM_OPS(auo_pixcir_pm_ops, auo_pixcir_suspend, -			 auo_pixcir_resume); +static SIMPLE_DEV_PM_OPS(auo_pixcir_pm_ops, +			 auo_pixcir_suspend, auo_pixcir_resume); + +#ifdef CONFIG_OF +static struct auo_pixcir_ts_platdata *auo_pixcir_parse_dt(struct device *dev) +{ +	struct auo_pixcir_ts_platdata *pdata; +	struct device_node *np = dev->of_node; + +	if (!np) +		return ERR_PTR(-ENOENT); + +	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); +	if (!pdata) { +		dev_err(dev, "failed to allocate platform data\n"); +		return ERR_PTR(-ENOMEM); +	} + +	pdata->gpio_int = of_get_gpio(np, 0); +	if (!gpio_is_valid(pdata->gpio_int)) { +		dev_err(dev, "failed to get interrupt gpio\n"); +		return ERR_PTR(-EINVAL); +	} + +	pdata->gpio_rst = of_get_gpio(np, 1); +	if (!gpio_is_valid(pdata->gpio_rst)) { +		dev_err(dev, "failed to get reset gpio\n"); +		return ERR_PTR(-EINVAL); +	} + +	if (of_property_read_u32(np, "x-size", &pdata->x_max)) { +		dev_err(dev, "failed to get x-size property\n"); +		return ERR_PTR(-EINVAL); +	} + +	if (of_property_read_u32(np, "y-size", &pdata->y_max)) { +		dev_err(dev, "failed to get y-size property\n"); +		return ERR_PTR(-EINVAL); +	} + +	/* default to asserting the interrupt when the screen is touched */ +	pdata->int_setting = AUO_PIXCIR_INT_TOUCH_IND; + +	return pdata; +} +#else +static struct auo_pixcir_ts_platdata *auo_pixcir_parse_dt(struct device *dev) +{ +	return ERR_PTR(-EINVAL); +} +#endif + +static void auo_pixcir_reset(void *data) +{ +	struct auo_pixcir_ts *ts = data; + +	gpio_set_value(ts->pdata->gpio_rst, 0); +}  static int auo_pixcir_probe(struct i2c_client *client, -				      const struct i2c_device_id *id) +			    const struct i2c_device_id *id)  { -	const struct auo_pixcir_ts_platdata *pdata = client->dev.platform_data; +	const struct auo_pixcir_ts_platdata *pdata;  	struct auo_pixcir_ts *ts;  	struct input_dev *input_dev; -	int ret; +	int version; +	int error; -	if (!pdata) -		return -EINVAL; +	pdata = dev_get_platdata(&client->dev); +	if (!pdata) { +		pdata = auo_pixcir_parse_dt(&client->dev); +		if (IS_ERR(pdata)) +			return PTR_ERR(pdata); +	} -	ts = kzalloc(sizeof(struct auo_pixcir_ts), GFP_KERNEL); +	ts = devm_kzalloc(&client->dev, +			  sizeof(struct auo_pixcir_ts), GFP_KERNEL);  	if (!ts)  		return -ENOMEM; -	ret = gpio_request(pdata->gpio_int, "auo_pixcir_ts_int"); -	if (ret) { -		dev_err(&client->dev, "request of gpio %d failed, %d\n", -			pdata->gpio_int, ret); -		goto err_gpio_int; +	input_dev = devm_input_allocate_device(&client->dev); +	if (!input_dev) { +		dev_err(&client->dev, "could not allocate input device\n"); +		return -ENOMEM;  	} -	if (pdata->init_hw) -		pdata->init_hw(client); - +	ts->pdata = pdata;  	ts->client = client; +	ts->input = input_dev;  	ts->touch_ind_mode = 0; +	ts->stopped = true;  	init_waitqueue_head(&ts->wait);  	snprintf(ts->phys, sizeof(ts->phys),  		 "%s/input0", dev_name(&client->dev)); -	input_dev = input_allocate_device(); -	if (!input_dev) { -		dev_err(&client->dev, "could not allocate input device\n"); -		goto err_input_alloc; -	} - -	ts->input = input_dev; -  	input_dev->name = "AUO-Pixcir touchscreen";  	input_dev->phys = ts->phys;  	input_dev->id.bustype = BUS_I2C; -	input_dev->dev.parent = &client->dev;  	input_dev->open = auo_pixcir_input_open;  	input_dev->close = auo_pixcir_input_close; @@ -550,70 +604,70 @@ static int auo_pixcir_probe(struct i2c_client *client,  			     AUO_PIXCIR_MAX_AREA, 0, 0);  	input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0); -	ret = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_VERSION); -	if (ret < 0) -		goto err_fw_vers; -	dev_info(&client->dev, "firmware version 0x%X\n", ret); - -	ret = auo_pixcir_int_config(ts, pdata->int_setting); -	if (ret) -		goto err_fw_vers; -  	input_set_drvdata(ts->input, ts); -	ts->stopped = true; -	ret = request_threaded_irq(client->irq, NULL, auo_pixcir_interrupt, -				   IRQF_TRIGGER_RISING | IRQF_ONESHOT, -				   input_dev->name, ts); -	if (ret) { -		dev_err(&client->dev, "irq %d requested failed\n", client->irq); -		goto err_fw_vers; +	error = devm_gpio_request_one(&client->dev, pdata->gpio_int, +				      GPIOF_DIR_IN, "auo_pixcir_ts_int"); +	if (error) { +		dev_err(&client->dev, "request of gpio %d failed, %d\n", +			pdata->gpio_int, error); +		return error;  	} -	/* stop device and put it into deep sleep until it is opened */ -	ret = auo_pixcir_stop(ts); -	if (ret < 0) -		goto err_input_register; - -	ret = input_register_device(input_dev); -	if (ret) { -		dev_err(&client->dev, "could not register input device\n"); -		goto err_input_register; +	error = devm_gpio_request_one(&client->dev, pdata->gpio_rst, +				      GPIOF_DIR_OUT | GPIOF_INIT_HIGH, +				      "auo_pixcir_ts_rst"); +	if (error) { +		dev_err(&client->dev, "request of gpio %d failed, %d\n", +			pdata->gpio_rst, error); +		return error;  	} -	i2c_set_clientdata(client, ts); - -	return 0; +	error = devm_add_action(&client->dev, auo_pixcir_reset, ts); +	if (error) { +		auo_pixcir_reset(ts); +		dev_err(&client->dev, "failed to register reset action, %d\n", +			error); +		return error; +	} -err_input_register: -	free_irq(client->irq, ts); -err_fw_vers: -	input_free_device(input_dev); -err_input_alloc: -	if (pdata->exit_hw) -		pdata->exit_hw(client); -	gpio_free(pdata->gpio_int); -err_gpio_int: -	kfree(ts); +	msleep(200); -	return ret; -} +	version = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_VERSION); +	if (version < 0) { +		error = version; +		return error; +	} -static int auo_pixcir_remove(struct i2c_client *client) -{ -	struct auo_pixcir_ts *ts = i2c_get_clientdata(client); -	const struct auo_pixcir_ts_platdata *pdata = client->dev.platform_data; +	dev_info(&client->dev, "firmware version 0x%X\n", version); -	free_irq(client->irq, ts); +	error = auo_pixcir_int_config(ts, pdata->int_setting); +	if (error) +		return error; -	input_unregister_device(ts->input); +	error = devm_request_threaded_irq(&client->dev, client->irq, +					  NULL, auo_pixcir_interrupt, +					  IRQF_TRIGGER_RISING | IRQF_ONESHOT, +					  input_dev->name, ts); +	if (error) { +		dev_err(&client->dev, "irq %d requested failed, %d\n", +			client->irq, error); +		return error; +	} -	if (pdata->exit_hw) -		pdata->exit_hw(client); +	/* stop device and put it into deep sleep until it is opened */ +	error = auo_pixcir_stop(ts); +	if (error) +		return error; -	gpio_free(pdata->gpio_int); +	error = input_register_device(input_dev); +	if (error) { +		dev_err(&client->dev, "could not register input device, %d\n", +			error); +		return error; +	} -	kfree(ts); +	i2c_set_clientdata(client, ts);  	return 0;  } @@ -624,14 +678,22 @@ static const struct i2c_device_id auo_pixcir_idtable[] = {  };  MODULE_DEVICE_TABLE(i2c, auo_pixcir_idtable); +#ifdef CONFIG_OF +static struct of_device_id auo_pixcir_ts_dt_idtable[] = { +	{ .compatible = "auo,auo_pixcir_ts" }, +	{}, +}; +MODULE_DEVICE_TABLE(of, auo_pixcir_ts_dt_idtable); +#endif +  static struct i2c_driver auo_pixcir_driver = {  	.driver = {  		.owner	= THIS_MODULE,  		.name	= "auo_pixcir_ts",  		.pm	= &auo_pixcir_pm_ops, +		.of_match_table	= of_match_ptr(auo_pixcir_ts_dt_idtable),  	},  	.probe		= auo_pixcir_probe, -	.remove		= auo_pixcir_remove,  	.id_table	= auo_pixcir_idtable,  }; diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index a9170157b44..83fa1b15a97 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -440,8 +440,7 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)  		return -EIO;  	} -	if (tsdata->raw_buffer) -		kfree(tsdata->raw_buffer); +	kfree(tsdata->raw_buffer);  	tsdata->raw_buffer = NULL;  	/* restore parameters */ diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c index 55255a94007..8fe5086c8d2 100644 --- a/drivers/input/touchscreen/eeti_ts.c +++ b/drivers/input/touchscreen/eeti_ts.c @@ -206,8 +206,7 @@ static int eeti_ts_probe(struct i2c_client *client,  	if (err < 0)  		goto err1; -	if (pdata) -		priv->irq_active_high = pdata->irq_active_high; +	priv->irq_active_high = pdata->irq_active_high;  	irq_flags = priv->irq_active_high ?  		IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING; diff --git a/drivers/input/touchscreen/mc13783_ts.c b/drivers/input/touchscreen/mc13783_ts.c index 02103b6abb3..89308fe3875 100644 --- a/drivers/input/touchscreen/mc13783_ts.c +++ b/drivers/input/touchscreen/mc13783_ts.c @@ -250,17 +250,7 @@ static struct platform_driver mc13783_ts_driver = {  	},  }; -static int __init mc13783_ts_init(void) -{ -	return platform_driver_probe(&mc13783_ts_driver, &mc13783_ts_probe); -} -module_init(mc13783_ts_init); - -static void __exit mc13783_ts_exit(void) -{ -	platform_driver_unregister(&mc13783_ts_driver); -} -module_exit(mc13783_ts_exit); +module_platform_driver_probe(mc13783_ts_driver, mc13783_ts_probe);  MODULE_DESCRIPTION("MC13783 input touchscreen driver");  MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/input/touchscreen/st1232.c b/drivers/input/touchscreen/st1232.c index d9d05e22242..1740a249637 100644 --- a/drivers/input/touchscreen/st1232.c +++ b/drivers/input/touchscreen/st1232.c @@ -19,13 +19,16 @@   */  #include <linux/delay.h> +#include <linux/gpio.h>  #include <linux/i2c.h>  #include <linux/input.h>  #include <linux/interrupt.h>  #include <linux/module.h> +#include <linux/of_gpio.h>  #include <linux/pm_qos.h>  #include <linux/slab.h>  #include <linux/types.h> +#include <linux/platform_data/st1232_pdata.h>  #define ST1232_TS_NAME	"st1232-ts" @@ -48,6 +51,7 @@ struct st1232_ts_data {  	struct input_dev *input_dev;  	struct st1232_ts_finger finger[MAX_FINGERS];  	struct dev_pm_qos_request low_latency_req; +	int reset_gpio;  };  static int st1232_ts_read_data(struct st1232_ts_data *ts) @@ -139,10 +143,17 @@ end:  	return IRQ_HANDLED;  } +static void st1232_ts_power(struct st1232_ts_data *ts, bool poweron) +{ +	if (gpio_is_valid(ts->reset_gpio)) +		gpio_direction_output(ts->reset_gpio, poweron); +} +  static int st1232_ts_probe(struct i2c_client *client,  					const struct i2c_device_id *id)  {  	struct st1232_ts_data *ts; +	struct st1232_pdata *pdata = client->dev.platform_data;  	struct input_dev *input_dev;  	int error; @@ -156,17 +167,36 @@ static int st1232_ts_probe(struct i2c_client *client,  		return -EINVAL;  	} +	ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); +	if (!ts) +		return -ENOMEM; -	ts = kzalloc(sizeof(struct st1232_ts_data), GFP_KERNEL); -	input_dev = input_allocate_device(); -	if (!ts || !input_dev) { -		error = -ENOMEM; -		goto err_free_mem; -	} +	input_dev = devm_input_allocate_device(&client->dev); +	if (!input_dev) +		return -ENOMEM;  	ts->client = client;  	ts->input_dev = input_dev; +	if (pdata) +		ts->reset_gpio = pdata->reset_gpio; +	else if (client->dev.of_node) +		ts->reset_gpio = of_get_gpio(client->dev.of_node, 0); +	else +		ts->reset_gpio = -ENODEV; + +	if (gpio_is_valid(ts->reset_gpio)) { +		error = devm_gpio_request(&client->dev, ts->reset_gpio, NULL); +		if (error) { +			dev_err(&client->dev, +				"Unable to request GPIO pin %d.\n", +				ts->reset_gpio); +				return error; +		} +	} + +	st1232_ts_power(ts, true); +  	input_dev->name = "st1232-touchscreen";  	input_dev->id.bustype = BUS_I2C;  	input_dev->dev.parent = &client->dev; @@ -179,31 +209,26 @@ static int st1232_ts_probe(struct i2c_client *client,  	input_set_abs_params(input_dev, ABS_MT_POSITION_X, MIN_X, MAX_X, 0, 0);  	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, MIN_Y, MAX_Y, 0, 0); -	error = request_threaded_irq(client->irq, NULL, st1232_ts_irq_handler, -				     IRQF_ONESHOT, client->name, ts); +	error = devm_request_threaded_irq(&client->dev, client->irq, +					  NULL, st1232_ts_irq_handler, +					  IRQF_ONESHOT, +					  client->name, ts);  	if (error) {  		dev_err(&client->dev, "Failed to register interrupt\n"); -		goto err_free_mem; +		return error;  	}  	error = input_register_device(ts->input_dev);  	if (error) {  		dev_err(&client->dev, "Unable to register %s input device\n",  			input_dev->name); -		goto err_free_irq; +		return error;  	}  	i2c_set_clientdata(client, ts);  	device_init_wakeup(&client->dev, 1);  	return 0; - -err_free_irq: -	free_irq(client->irq, ts); -err_free_mem: -	input_free_device(input_dev); -	kfree(ts); -	return error;  }  static int st1232_ts_remove(struct i2c_client *client) @@ -211,9 +236,7 @@ static int st1232_ts_remove(struct i2c_client *client)  	struct st1232_ts_data *ts = i2c_get_clientdata(client);  	device_init_wakeup(&client->dev, 0); -	free_irq(client->irq, ts); -	input_unregister_device(ts->input_dev); -	kfree(ts); +	st1232_ts_power(ts, false);  	return 0;  } @@ -222,11 +245,14 @@ static int st1232_ts_remove(struct i2c_client *client)  static int st1232_ts_suspend(struct device *dev)  {  	struct i2c_client *client = to_i2c_client(dev); +	struct st1232_ts_data *ts = i2c_get_clientdata(client); -	if (device_may_wakeup(&client->dev)) +	if (device_may_wakeup(&client->dev)) {  		enable_irq_wake(client->irq); -	else +	} else {  		disable_irq(client->irq); +		st1232_ts_power(ts, false); +	}  	return 0;  } @@ -234,11 +260,14 @@ static int st1232_ts_suspend(struct device *dev)  static int st1232_ts_resume(struct device *dev)  {  	struct i2c_client *client = to_i2c_client(dev); +	struct st1232_ts_data *ts = i2c_get_clientdata(client); -	if (device_may_wakeup(&client->dev)) +	if (device_may_wakeup(&client->dev)) {  		disable_irq_wake(client->irq); -	else +	} else { +		st1232_ts_power(ts, true);  		enable_irq(client->irq); +	}  	return 0;  } diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c index 6e743e3dfda..16b52115c27 100644 --- a/drivers/input/touchscreen/wm9712.c +++ b/drivers/input/touchscreen/wm9712.c @@ -162,14 +162,14 @@ static void wm9712_phy_init(struct wm97xx *wm)  	if (rpu) {  		dig2 &= 0xffc0;  		dig2 |= WM9712_RPU(rpu); -		dev_dbg(wm->dev, "setting pen detect pull-up to %d Ohms", +		dev_dbg(wm->dev, "setting pen detect pull-up to %d Ohms\n",  			64000 / rpu);  	}  	/* WM9712 five wire */  	if (five_wire) {  		dig2 |= WM9712_45W; -		dev_dbg(wm->dev, "setting 5-wire touchscreen mode."); +		dev_dbg(wm->dev, "setting 5-wire touchscreen mode.\n");  		if (pil) {  			dev_warn(wm->dev, "pressure measurement is not " @@ -182,21 +182,21 @@ static void wm9712_phy_init(struct wm97xx *wm)  	if (pil == 2) {  		dig2 |= WM9712_PIL;  		dev_dbg(wm->dev, -			"setting pressure measurement current to 400uA."); +			"setting pressure measurement current to 400uA.\n");  	} else if (pil)  		dev_dbg(wm->dev, -			"setting pressure measurement current to 200uA."); +			"setting pressure measurement current to 200uA.\n");  	if (!pil)  		pressure = 0;  	/* polling mode sample settling delay */  	if (delay < 0 || delay > 15) { -		dev_dbg(wm->dev, "supplied delay out of range."); +		dev_dbg(wm->dev, "supplied delay out of range.\n");  		delay = 4;  	}  	dig1 &= 0xff0f;  	dig1 |= WM97XX_DELAY(delay); -	dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.", +	dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.\n",  		delay_table[delay]);  	/* mask */ @@ -285,7 +285,7 @@ static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample)  		if (is_pden(wm))  			wm->pen_probably_down = 0;  		else -			dev_dbg(wm->dev, "adc sample timeout"); +			dev_dbg(wm->dev, "adc sample timeout\n");  		return RC_PENUP;  	} @@ -295,15 +295,19 @@ static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample)  	/* check we have correct sample */  	if ((*sample ^ adcsel) & WM97XX_ADCSEL_MASK) { -		dev_dbg(wm->dev, "adc wrong sample, wanted %x got %x", +		dev_dbg(wm->dev, "adc wrong sample, wanted %x got %x\n",  			adcsel & WM97XX_ADCSEL_MASK,  			*sample & WM97XX_ADCSEL_MASK); -		return RC_PENUP; +		return RC_AGAIN;  	}  	if (wants_pen && !(*sample & WM97XX_PEN_DOWN)) { -		wm->pen_probably_down = 0; -		return RC_PENUP; +		/* Sometimes it reads a wrong value the first time. */ +		*sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); +		if (!(*sample & WM97XX_PEN_DOWN)) { +			wm->pen_probably_down = 0; +			return RC_PENUP; +		}  	}  	return RC_VALID; @@ -345,7 +349,7 @@ static int wm9712_poll_coord(struct wm97xx *wm, struct wm97xx_data *data)  		if (is_pden(wm))  			wm->pen_probably_down = 0;  		else -			dev_dbg(wm->dev, "adc sample timeout"); +			dev_dbg(wm->dev, "adc sample timeout\n");  		return RC_PENUP;  	} diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c index 5dbe73af2f8..7e45c9f6e6b 100644 --- a/drivers/input/touchscreen/wm97xx-core.c +++ b/drivers/input/touchscreen/wm97xx-core.c @@ -442,6 +442,16 @@ static int wm97xx_read_samples(struct wm97xx *wm)  			"pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n",  			data.x >> 12, data.x & 0xfff, data.y >> 12,  			data.y & 0xfff, data.p >> 12, data.p & 0xfff); + +		if (abs_x[0] > (data.x & 0xfff) || +		    abs_x[1] < (data.x & 0xfff) || +		    abs_y[0] > (data.y & 0xfff) || +		    abs_y[1] < (data.y & 0xfff)) { +			dev_dbg(wm->dev, "Measurement out of range, dropping it\n"); +			rc = RC_AGAIN; +			goto out; +		} +  		input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff);  		input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff);  		input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff); @@ -455,6 +465,7 @@ static int wm97xx_read_samples(struct wm97xx *wm)  		wm->ts_reader_interval = wm->ts_reader_min_interval;  	} +out:  	mutex_unlock(&wm->codec_mutex);  	return rc;  } diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c index 0a0de333c76..b51c15408ff 100644 --- a/drivers/tty/sysrq.c +++ b/drivers/tty/sysrq.c @@ -43,6 +43,7 @@  #include <linux/input.h>  #include <linux/uaccess.h>  #include <linux/moduleparam.h> +#include <linux/jiffies.h>  #include <asm/ptrace.h>  #include <asm/irq_regs.h> @@ -51,6 +52,9 @@  static int __read_mostly sysrq_enabled = SYSRQ_DEFAULT_ENABLE;  static bool __read_mostly sysrq_always_enabled; +unsigned short platform_sysrq_reset_seq[] __weak = { KEY_RESERVED }; +int sysrq_reset_downtime_ms __weak; +  static bool sysrq_on(void)  {  	return sysrq_enabled || sysrq_always_enabled; @@ -586,6 +590,7 @@ struct sysrq_state {  	int reset_seq_len;  	int reset_seq_cnt;  	int reset_seq_version; +	struct timer_list keyreset_timer;  };  #define SYSRQ_KEY_RESET_MAX	20 /* Should be plenty */ @@ -619,29 +624,51 @@ static void sysrq_parse_reset_sequence(struct sysrq_state *state)  	state->reset_seq_version = sysrq_reset_seq_version;  } -static bool sysrq_detect_reset_sequence(struct sysrq_state *state, +static void sysrq_do_reset(unsigned long dummy) +{ +	__handle_sysrq(sysrq_xlate[KEY_B], false); +} + +static void sysrq_handle_reset_request(struct sysrq_state *state) +{ +	if (sysrq_reset_downtime_ms) +		mod_timer(&state->keyreset_timer, +			jiffies + msecs_to_jiffies(sysrq_reset_downtime_ms)); +	else +		sysrq_do_reset(0); +} + +static void sysrq_detect_reset_sequence(struct sysrq_state *state,  					unsigned int code, int value)  {  	if (!test_bit(code, state->reset_keybit)) {  		/*  		 * Pressing any key _not_ in reset sequence cancels -		 * the reset sequence. +		 * the reset sequence.  Also cancelling the timer in +		 * case additional keys were pressed after a reset +		 * has been requested.  		 */ -		if (value && state->reset_seq_cnt) +		if (value && state->reset_seq_cnt) {  			state->reset_canceled = true; +			del_timer(&state->keyreset_timer); +		}  	} else if (value == 0) { -		/* key release */ +		/* +		 * Key release - all keys in the reset sequence need +		 * to be pressed and held for the reset timeout +		 * to hold. +		 */ +		del_timer(&state->keyreset_timer); +  		if (--state->reset_seq_cnt == 0)  			state->reset_canceled = false;  	} else if (value == 1) {  		/* key press, not autorepeat */  		if (++state->reset_seq_cnt == state->reset_seq_len &&  		    !state->reset_canceled) { -			return true; +			sysrq_handle_reset_request(state);  		}  	} - -	return false;  }  static void sysrq_reinject_alt_sysrq(struct work_struct *work) @@ -748,10 +775,8 @@ static bool sysrq_handle_keypress(struct sysrq_state *sysrq,  		if (was_active)  			schedule_work(&sysrq->reinject_work); -		if (sysrq_detect_reset_sequence(sysrq, code, value)) { -			/* Force emergency reboot */ -			__handle_sysrq(sysrq_xlate[KEY_B], false); -		} +		/* Check for reset sequence */ +		sysrq_detect_reset_sequence(sysrq, code, value);  	} else if (value == 0 && test_and_clear_bit(code, sysrq->key_down)) {  		/* @@ -812,6 +837,7 @@ static int sysrq_connect(struct input_handler *handler,  	sysrq->handle.handler = handler;  	sysrq->handle.name = "sysrq";  	sysrq->handle.private = sysrq; +	setup_timer(&sysrq->keyreset_timer, sysrq_do_reset, 0);  	error = input_register_handle(&sysrq->handle);  	if (error) { @@ -841,6 +867,7 @@ static void sysrq_disconnect(struct input_handle *handle)  	input_close_device(handle);  	cancel_work_sync(&sysrq->reinject_work); +	del_timer_sync(&sysrq->keyreset_timer);  	input_unregister_handle(handle);  	kfree(sysrq);  } @@ -870,8 +897,6 @@ static struct input_handler sysrq_handler = {  static bool sysrq_handler_registered; -unsigned short platform_sysrq_reset_seq[] __weak = { KEY_RESERVED }; -  static inline void sysrq_register_handler(void)  {  	unsigned short key; @@ -931,6 +956,8 @@ static struct kernel_param_ops param_ops_sysrq_reset_seq = {  module_param_array_named(reset_seq, sysrq_reset_seq, sysrq_reset_seq,  			 &sysrq_reset_seq_len, 0644); +module_param_named(sysrq_downtime_ms, sysrq_reset_downtime_ms, int, 0644); +  #else  static inline void sysrq_register_handler(void) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 171d7a9df3a..9b1cbcf8fb7 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -962,6 +962,10 @@ static int acm_probe(struct usb_interface *intf,  	/* normal quirks */  	quirks = (unsigned long)id->driver_info; + +	if (quirks == IGNORE_DEVICE) +		return -ENODEV; +  	num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR;  	/* handle quirks deadly to normal probing*/ @@ -1675,6 +1679,15 @@ static const struct usb_device_id acm_ids[] = {  	.driver_info = NO_DATA_INTERFACE,  	}, +#if IS_ENABLED(CONFIG_INPUT_IMS_PCU) +	{ USB_DEVICE(0x04d8, 0x0082),	/* Application mode */ +	.driver_info = IGNORE_DEVICE, +	}, +	{ USB_DEVICE(0x04d8, 0x0083),	/* Bootloader mode */ +	.driver_info = IGNORE_DEVICE, +	}, +#endif +  	/* control interfaces without any protocol set */  	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,  		USB_CDC_PROTO_NONE) }, diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 35ef887b741..0f76e4af600 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -128,3 +128,4 @@ struct acm {  #define NO_CAP_LINE			4  #define NOT_A_MODEM			8  #define NO_DATA_INTERFACE		16 +#define IGNORE_DEVICE			32 diff --git a/include/linux/device.h b/include/linux/device.h index 711793b145f..c0a12612532 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -576,6 +576,10 @@ void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res);  void __iomem *devm_request_and_ioremap(struct device *dev,  			struct resource *res); +/* allows to add/remove a custom action to devres stack */ +int devm_add_action(struct device *dev, void (*action)(void *), void *data); +void devm_remove_action(struct device *dev, void (*action)(void *), void *data); +  struct device_dma_parameters {  	/*  	 * a low level driver may set these to teach IOMMU code about diff --git a/include/linux/input/auo-pixcir-ts.h b/include/linux/input/auo-pixcir-ts.h index 75d4be71771..5049f21928e 100644 --- a/include/linux/input/auo-pixcir-ts.h +++ b/include/linux/input/auo-pixcir-ts.h @@ -43,12 +43,10 @@   */  struct auo_pixcir_ts_platdata {  	int gpio_int; +	int gpio_rst;  	int int_setting; -	void (*init_hw)(struct i2c_client *); -	void (*exit_hw)(struct i2c_client *); -  	unsigned int x_max;  	unsigned int y_max;  }; diff --git a/include/linux/input/mt.h b/include/linux/input/mt.h index 2e86bd0bfba..1b1dfa80d9f 100644 --- a/include/linux/input/mt.h +++ b/include/linux/input/mt.h @@ -19,6 +19,7 @@  #define INPUT_MT_DIRECT		0x0002	/* direct device, e.g. touchscreen */  #define INPUT_MT_DROP_UNUSED	0x0004	/* drop contacts not seen in frame */  #define INPUT_MT_TRACK		0x0008	/* use in-kernel tracking */ +#define INPUT_MT_SEMI_MT	0x0010	/* semi-mt device, finger count handled manually */  /**   * struct input_mt_slot - represents the state of an input MT slot diff --git a/include/linux/platform_data/st1232_pdata.h b/include/linux/platform_data/st1232_pdata.h new file mode 100644 index 00000000000..cac3e7b4c45 --- /dev/null +++ b/include/linux/platform_data/st1232_pdata.h @@ -0,0 +1,13 @@ +#ifndef _LINUX_ST1232_PDATA_H +#define _LINUX_ST1232_PDATA_H + +/* + * Optional platform data + * + * Use this if you want the driver to drive the reset pin. + */ +struct st1232_pdata { +	int reset_gpio; +}; + +#endif diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index 935119c698a..4649ee35b60 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -702,6 +702,11 @@ struct input_keymap_entry {  #define KEY_CAMERA_LEFT		0x219  #define KEY_CAMERA_RIGHT	0x21a +#define KEY_ATTENDANT_ON	0x21b +#define KEY_ATTENDANT_OFF	0x21c +#define KEY_ATTENDANT_TOGGLE	0x21d	/* Attendant call on or off */ +#define KEY_LIGHTS_TOGGLE	0x21e	/* Reading light on or off */ +  #define BTN_TRIGGER_HAPPY		0x2c0  #define BTN_TRIGGER_HAPPY1		0x2c0  #define BTN_TRIGGER_HAPPY2		0x2c1  |