From fa656308622fdaf54f2ff9ce8a70e4260f701b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20St=C3=BCbner?= Date: Sat, 23 Feb 2013 12:06:34 -0800 Subject: Input: auo-pixcir-ts - set input direction for interrupt gpio Previously the gpio was not configured at all. Signed-off-by: Heiko Stuebner Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/auo-pixcir-ts.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c index c6e19a96348e..813413eebab7 100644 --- a/drivers/input/touchscreen/auo-pixcir-ts.c +++ b/drivers/input/touchscreen/auo-pixcir-ts.c @@ -504,6 +504,13 @@ static int auo_pixcir_probe(struct i2c_client *client, goto err_gpio_int; } + ret = gpio_direction_input(pdata->gpio_int); + if (ret) { + dev_err(&client->dev, "setting direction of gpio %d failed %d\n", + pdata->gpio_int, ret); + goto err_gpio_dir; + } + if (pdata->init_hw) pdata->init_hw(client); @@ -592,6 +599,7 @@ err_fw_vers: err_input_alloc: if (pdata->exit_hw) pdata->exit_hw(client); +err_gpio_dir: gpio_free(pdata->gpio_int); err_gpio_int: kfree(ts); -- cgit v1.2.3 From 27cef8b47cfb27fa2955a8577637794f1f275db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20St=C3=BCbner?= Date: Sat, 23 Feb 2013 12:06:44 -0800 Subject: Input: auo-pixcir-ts - handle reset gpio directly Devicetree based platforms don't handle device callbacks very well and until now no board has come along that needs more extended hwinit than pulling the rst gpio high. Therefore pull the reset handling directly into the driver and remove the callbacks from the driver. If extended device setup is needed at some later point, power-sequences would probably be the solution of choice. Signed-off-by: Heiko Stuebner Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/auo-pixcir-ts.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c index 813413eebab7..6317a9c7884c 100644 --- a/drivers/input/touchscreen/auo-pixcir-ts.c +++ b/drivers/input/touchscreen/auo-pixcir-ts.c @@ -511,8 +511,21 @@ static int auo_pixcir_probe(struct i2c_client *client, goto err_gpio_dir; } - if (pdata->init_hw) - pdata->init_hw(client); + ret = gpio_request(pdata->gpio_rst, "auo_pixcir_ts_rst"); + if (ret) { + dev_err(&client->dev, "request of gpio %d failed, %d\n", + pdata->gpio_rst, ret); + goto err_gpio_dir; + } + + ret = gpio_direction_output(pdata->gpio_rst, 1); + if (ret) { + dev_err(&client->dev, "setting direction of gpio %d failed %d\n", + pdata->gpio_rst, ret); + goto err_gpio_rst; + } + + msleep(200); ts->client = client; ts->touch_ind_mode = 0; @@ -597,8 +610,9 @@ err_input_register: err_fw_vers: input_free_device(input_dev); err_input_alloc: - if (pdata->exit_hw) - pdata->exit_hw(client); + gpio_set_value(pdata->gpio_rst, 0); +err_gpio_rst: + gpio_free(pdata->gpio_rst); err_gpio_dir: gpio_free(pdata->gpio_int); err_gpio_int: @@ -616,8 +630,8 @@ static int auo_pixcir_remove(struct i2c_client *client) input_unregister_device(ts->input); - if (pdata->exit_hw) - pdata->exit_hw(client); + gpio_set_value(pdata->gpio_rst, 0); + gpio_free(pdata->gpio_rst); gpio_free(pdata->gpio_int); -- cgit v1.2.3 From 38e83f7f9169400047aa3033d643891da1b00441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20St=C3=BCbner?= Date: Sat, 23 Feb 2013 12:06:48 -0800 Subject: Input: auo_pixcir_ts - keep own pointer to platform_data When supporting devicetree the platformdata may not necessarily come from the dev but may be generated in the driver instead. Therefore keep the pointer in the driver struct instead of using dev.platform_data. Signed-off-by: Heiko Stuebner Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/auo-pixcir-ts.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c index 6317a9c7884c..c0a2483b30cb 100644 --- a/drivers/input/touchscreen/auo-pixcir-ts.c +++ b/drivers/input/touchscreen/auo-pixcir-ts.c @@ -111,6 +111,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 +133,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 +179,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 +290,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); @@ -527,6 +527,7 @@ static int auo_pixcir_probe(struct i2c_client *client, msleep(200); + ts->pdata = pdata; ts->client = client; ts->touch_ind_mode = 0; init_waitqueue_head(&ts->wait); @@ -624,7 +625,7 @@ err_gpio_int: 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; + const struct auo_pixcir_ts_platdata *pdata = ts->pdata; free_irq(client->irq, ts); -- cgit v1.2.3 From 7241443f67bb352183bcfd7e3b807b87f2777b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20St=C3=BCbner?= Date: Sat, 23 Feb 2013 12:08:55 -0800 Subject: Input: auo_pixcir_ts - add devicetree support Add the necessary code to create the needed platformdata from devicetree informations. The interrupt mode of the chip is not set via devicetree, as it is not a property of the hardware but instead only a preferred type of operation. This should probably be made settable via configfs in the future. The option set as default is the mode the datasheet mentions as default. Signed-off-by: Heiko Stuebner Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/auo-pixcir-ts.c | 74 ++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c index c0a2483b30cb..dfa6d5463ef7 100644 --- a/drivers/input/touchscreen/auo-pixcir-ts.c +++ b/drivers/input/touchscreen/auo-pixcir-ts.c @@ -31,6 +31,8 @@ #include #include #include +#include +#include /* * Coordinate calculation: @@ -479,19 +481,72 @@ 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 int auo_pixcir_probe(struct i2c_client *client, 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; - 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); if (!ts) @@ -647,11 +702,20 @@ 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, -- cgit v1.2.3 From d6b0c58048d2c8c6f4955c37f670125b2792cd14 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 23 Feb 2013 13:11:14 -0800 Subject: devres: allow adding custom actions to the stack Sometimes drivers need to execute one-off actions in their error handling or device teardown paths. An example would be toggling a GPIO line to reset the controlled device into predefined state. To allow performing such actions when using managed resources let's allow adding them to stack/group of devres resources. Acked-by: Tejun Heo Acked-by: Greg Kroah-Hartman Signed-off-by: Dmitry Torokhov --- drivers/base/devres.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) (limited to 'drivers') diff --git a/drivers/base/devres.c b/drivers/base/devres.c index 8731979d668a..724957a13d48 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -670,6 +670,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 */ -- cgit v1.2.3 From bfc29e95950345dca0d64a79da46f6b8592c0b1e Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 23 Feb 2013 12:18:14 -0800 Subject: Input: auo-pixcir-ts - switch to using managed resources This simplifies error unwinding and device teardown. Tested-by: Heiko Stuebner Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/auo-pixcir-ts.c | 163 +++++++++++++----------------- 1 file changed, 69 insertions(+), 94 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c index dfa6d5463ef7..d3f9f6b0f9b7 100644 --- a/drivers/input/touchscreen/auo-pixcir-ts.c +++ b/drivers/input/touchscreen/auo-pixcir-ts.c @@ -533,13 +533,21 @@ static struct auo_pixcir_ts_platdata *auo_pixcir_parse_dt(struct device *dev) } #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; struct auo_pixcir_ts *ts; struct input_dev *input_dev; - int ret; + int version; + int error; pdata = dev_get_platdata(&client->dev); if (!pdata) { @@ -548,60 +556,30 @@ static int auo_pixcir_probe(struct i2c_client *client, 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; - } - - ret = gpio_direction_input(pdata->gpio_int); - if (ret) { - dev_err(&client->dev, "setting direction of gpio %d failed %d\n", - pdata->gpio_int, ret); - goto err_gpio_dir; - } - - ret = gpio_request(pdata->gpio_rst, "auo_pixcir_ts_rst"); - if (ret) { - dev_err(&client->dev, "request of gpio %d failed, %d\n", - pdata->gpio_rst, ret); - goto err_gpio_dir; - } - - ret = gpio_direction_output(pdata->gpio_rst, 1); - if (ret) { - dev_err(&client->dev, "setting direction of gpio %d failed %d\n", - pdata->gpio_rst, ret); - goto err_gpio_rst; + input_dev = devm_input_allocate_device(&client->dev); + if (!input_dev) { + dev_err(&client->dev, "could not allocate input device\n"); + return -ENOMEM; } - msleep(200); - 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; @@ -626,72 +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; - -err_input_register: - free_irq(client->irq, ts); -err_fw_vers: - input_free_device(input_dev); -err_input_alloc: - gpio_set_value(pdata->gpio_rst, 0); -err_gpio_rst: - gpio_free(pdata->gpio_rst); -err_gpio_dir: - gpio_free(pdata->gpio_int); -err_gpio_int: - kfree(ts); + 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; + } - return ret; -} + msleep(200); -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 = ts->pdata; + version = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_VERSION); + if (version < 0) { + error = version; + return error; + } - free_irq(client->irq, ts); + dev_info(&client->dev, "firmware version 0x%X\n", version); - input_unregister_device(ts->input); + error = auo_pixcir_int_config(ts, pdata->int_setting); + if (error) + return error; - gpio_set_value(pdata->gpio_rst, 0); - gpio_free(pdata->gpio_rst); + 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; + } - gpio_free(pdata->gpio_int); + /* stop device and put it into deep sleep until it is opened */ + error = auo_pixcir_stop(ts); + if (error) + return error; + + 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; } @@ -718,7 +694,6 @@ static struct i2c_driver auo_pixcir_driver = { .of_match_table = of_match_ptr(auo_pixcir_ts_dt_idtable), }, .probe = auo_pixcir_probe, - .remove = auo_pixcir_remove, .id_table = auo_pixcir_idtable, }; -- cgit v1.2.3 From b4a034dab147776eab8eb8b2997ea16ef0e32c17 Mon Sep 17 00:00:00 2001 From: Daniel Hellstrom Date: Mon, 25 Feb 2013 22:51:37 -0800 Subject: Input: add support for GRLIB APBPS2 PS/2 Keyboard/Mouse APBPS2 is a PS/2 core part of GRLIB found in SPARC32/LEON products. Signed-off-by: Daniel Hellstrom Signed-off-by: Dmitry Torokhov --- drivers/input/serio/Kconfig | 10 ++ drivers/input/serio/Makefile | 1 + drivers/input/serio/apbps2.c | 230 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 drivers/input/serio/apbps2.c (limited to 'drivers') diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index 560c243bfcaf..dbb170916dd1 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -244,4 +244,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 4b0c8f84f1c1..8edb36c2cdb4 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 000000000000..2c14e6fa64c2 --- /dev/null +++ b/drivers/input/serio/apbps2.c @@ -0,0 +1,230 @@ +/* + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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_request_and_ioremap(&ofdev->dev, res); + if (!priv->regs) { + dev_err(&ofdev->dev, "io-regs mapping failed\n"); + return -EBUSY; + } + + /* 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"); -- cgit v1.2.3 From cfd5d09691ee188a36c00f5c3b220fbf082a78d7 Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Sat, 9 Mar 2013 15:19:56 -0800 Subject: Input: wm97xx - drop out of range inputs With fast movements, there occured some out of screen jumps with my touchscreen. The abs_x and abs_y module parameters should fix this by default, but the driver doesn't actively checks the x/y coordinates. Instead it seems that the input layer was supposed to drop out of range inputs, as described in the comments: "These parameters are used to help the input layer discard out of range readings and reduce jitter etc" The input layer documentation describes that values that are not in the absolute range are also accepted. So this patch adds a check within the driver. Signed-off-by: Markus Pargmann Acked-by: Mark Brown Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/wm97xx-core.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers') diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c index 5dbe73af2f8f..7e45c9f6e6b7 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; } -- cgit v1.2.3 From fa45255ee72649bd061116f7d3a765f5784bf078 Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Sat, 9 Mar 2013 15:20:50 -0800 Subject: Input: wm9712 - fix return code for wrong sample MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of interpreting a wrong measurement as pen up, we should try to read again. Based on wm9712: pen up by Teresa Gámez and Christian Hemp. Signed-off-by: Markus Pargmann Acked-by: Mark Brown Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/wm9712.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c index 6e743e3dfda4..a983da1744e8 100644 --- a/drivers/input/touchscreen/wm9712.c +++ b/drivers/input/touchscreen/wm9712.c @@ -298,7 +298,7 @@ static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample) dev_dbg(wm->dev, "adc wrong sample, wanted %x got %x", adcsel & WM97XX_ADCSEL_MASK, *sample & WM97XX_ADCSEL_MASK); - return RC_PENUP; + return RC_AGAIN; } if (wants_pen && !(*sample & WM97XX_PEN_DOWN)) { -- cgit v1.2.3 From 540792753c292bce53f42eb7b8fb3d3510f57e00 Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Sat, 9 Mar 2013 15:21:33 -0800 Subject: Input: wm9712 - fix wrong pen up readings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Often a reading can be wrong. This patch assures that this is really a pen up event and not a false reading. Based on wm9712: pen up by Teresa Gámez and Christian Hemp. Signed-off-by: Markus Pargmann Acked-by: Mark Brown Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/wm9712.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c index a983da1744e8..3b4eed4b8690 100644 --- a/drivers/input/touchscreen/wm9712.c +++ b/drivers/input/touchscreen/wm9712.c @@ -302,8 +302,12 @@ static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample) } 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; -- cgit v1.2.3 From d73a17e6c85010f2249e823231ccc0345d1e016f Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Sat, 9 Mar 2013 15:22:35 -0800 Subject: Input: wm9712 - fix dev_dbg newlines dev_dbg should end with a new line. Signed-off-by: Markus Pargmann Acked-by: Mark Brown Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/wm9712.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c index 3b4eed4b8690..16b52115c27f 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,7 +295,7 @@ 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_AGAIN; @@ -349,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; } -- cgit v1.2.3 From 628329d52474323938a03826941e166bc7c8eff4 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 2 Feb 2013 11:26:13 -0800 Subject: Input: add IMS Passenger Control Unit driver The PCU is a device installed in the armrest of a plane seat and is connected to IMS Rave Entertainment System. It has a set of control buttons (Volume Up/Down, Attendant, Lights, etc) on one side and gamepad-like controls on the other side. Originally the device was handled from userspace and because of that it presents itself on USB bus as a CDC-ACM modem device that however can not make calls. However the custom handling is not as convenient as using standard input subsystem facilities. If it was pure input device it would be possible to continue using userspace solution (moving it over to uinput), but the device also has backlighted keys which can not be supported via uinput. Signed-off-by: Dmitry Torokhov --- drivers/input/misc/Kconfig | 10 + drivers/input/misc/Makefile | 1 + drivers/input/misc/ims-pcu.c | 1900 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1911 insertions(+) create mode 100644 drivers/input/misc/ims-pcu.c (limited to 'drivers') diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 2a1647ef5610..3ec8887ce451 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -580,6 +580,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 1f874afeea6a..d6873433ba71 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -28,6 +28,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/ims-pcu.c b/drivers/input/misc/ims-pcu.c new file mode 100644 index 000000000000..1b044b99da66 --- /dev/null +++ b/drivers/input/misc/ims-pcu.c @@ -0,0 +1,1900 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); + return -ENOMEM; + } + + 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; + 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 "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 16142655269aaf580488e074eabfdcf0fb4e3687 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 2 Feb 2013 11:02:14 -0800 Subject: USB: cdc-acm - blacklist IMS PCU device The IMS PCU (Passenger Control Unit) device used custom protocol over serial line, so it is presenting itself as CDC ACM device. Now that we have proper in-kernel driver for it we need to black-list the device in cdc-acm driver. Acked-by: Greg Kroah-Hartman Signed-off-by: Dmitry Torokhov --- drivers/usb/class/cdc-acm.c | 13 +++++++++++++ drivers/usb/class/cdc-acm.h | 1 + 2 files changed, 14 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 6e49ec6f3adc..278bf5256a95 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -949,6 +949,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*/ @@ -1650,6 +1654,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 35ef887b7417..0f76e4af600e 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 -- cgit v1.2.3 From ba52a7fc434bea7c4a5d0ac7c10bf131f5ac60e2 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:30:05 -0700 Subject: Input: tegra-kbc - convert to devm_ioremap_resource() Use the newly introduced devm_ioremap_resource() instead of devm_request_and_ioremap() which provides more consistent error handling. devm_ioremap_resource() provides its own error messages; so all explicit error messages can be removed from the failure code paths. Signed-off-by: Sachin Kamat Reviewed-by: Thierry Reding Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/tegra-kbc.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/input/keyboard/tegra-kbc.c b/drivers/input/keyboard/tegra-kbc.c index 0e138ebcc768..68b2b65024d6 100644 --- a/drivers/input/keyboard/tegra-kbc.c +++ b/drivers/input/keyboard/tegra-kbc.c @@ -31,6 +31,7 @@ #include #include #include +#include #define KBC_MAX_GPIO 24 #define KBC_MAX_KPENT 8 @@ -608,11 +609,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)) { -- cgit v1.2.3 From 6b0bc60b8b87be28f00dc71e624bd693d830bf74 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:26:22 -0700 Subject: Input: davinci_keyscan - use module_platform_driver_probe macro module_platform_driver_probe() simplifies the code by eliminating boilerplate code. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/davinci_keyscan.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/input/keyboard/davinci_keyscan.c b/drivers/input/keyboard/davinci_keyscan.c index 4e4e453ea15e..829753702b62 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"); -- cgit v1.2.3 From 8c0aafe07721a3807f08d2a30abc1b04f01aa48b Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:26:44 -0700 Subject: Input: twl4030-pwrbutton - use module_platform_driver_probe macro module_platform_driver_probe() eliminates the boilerplate and simplifies the code. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/misc/twl4030-pwrbutton.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/input/misc/twl4030-pwrbutton.c b/drivers/input/misc/twl4030-pwrbutton.c index 27c2bc8aa890..b9a05fda03e4 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"); -- cgit v1.2.3 From e67d541ff2574ea30b046642217ac3ed98826e13 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:27:11 -0700 Subject: Input: amikbd - use module_platform_driver_probe macro module_platform_driver_probe() eliminates the boilerplate and simplifies the code. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/amikbd.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/input/keyboard/amikbd.c b/drivers/input/keyboard/amikbd.c index 79172af164f2..ba0b36f7daea 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"); -- cgit v1.2.3 From 0442ce1cff6597ac36003342d31565f7358e75df Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:27:41 -0700 Subject: Input: nomadik-ske-keypad - use module_platform_driver_probe macro module_platform_driver_probe() eliminates the boilerplate and simplifies the code. Signed-off-by: Sachin Kamat Acked-by: Linus Walleij Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/nomadik-ske-keypad.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 0e6a8151fee3..c7d505cce72f 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 / Sundar Iyer "); -- cgit v1.2.3 From e492fe2751dbb14e1a24f888e9b174bc9bafee21 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:28:00 -0700 Subject: Input: amimouse - use module_platform_driver_probe macro module_platform_driver_probe() eliminates the boilerplate and simplifies the code. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/amimouse.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/input/mouse/amimouse.c b/drivers/input/mouse/amimouse.c index 5fa99341a39d..b55d5af217a7 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"); -- cgit v1.2.3 From 9e984cdd171c0feaaf81af1f35c15439c7798bb8 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:28:14 -0700 Subject: Input: at32psif - use module_platform_driver_probe macro module_platform_driver_probe() eliminates the boilerplate and simplifies the code. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/serio/at32psif.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/input/serio/at32psif.c b/drivers/input/serio/at32psif.c index 36e799c31f5e..190ce35af7df 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 "); MODULE_DESCRIPTION("Atmel AVR32 PSIF PS/2 driver"); -- cgit v1.2.3 From 07beda4f6d449e045baa776ee3f31979e37d6c64 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:28:28 -0700 Subject: Input: q40kbd - use module_platform_driver_probe macro module_platform_driver_probe() eliminates the boilerplate and simplifies the code. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/serio/q40kbd.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/input/serio/q40kbd.c b/drivers/input/serio/q40kbd.c index 70fe542839fb..436a3433f8e5 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); -- cgit v1.2.3 From 8698a9382603faf64f045445b90f768522af28da Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Sun, 17 Mar 2013 21:29:07 -0700 Subject: Input: atmel-wm97xx - use module_platform_driver_probe macro module_platform_driver_probe() eliminates the boilerplate and simplifies the code. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/atmel-wm97xx.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/atmel-wm97xx.c b/drivers/input/touchscreen/atmel-wm97xx.c index c5c2dbb93869..2c1e46b7e45b 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 "); MODULE_DESCRIPTION("wm97xx continuous touch driver for Atmel AT91 and AVR32"); -- cgit v1.2.3 From 38a46eb8cc0faec0345c27083fb26d1e7e02ccff Mon Sep 17 00:00:00 2001 From: Fabio Porcedda Date: Sun, 17 Mar 2013 21:31:03 -0700 Subject: Input: mc13783_ts - use module_platform_driver_probe() This patch converts the drivers to use the module_platform_driver_probe() macro which makes the code smaller and a bit simpler. Signed-off-by: Fabio Porcedda Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mc13783_ts.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/mc13783_ts.c b/drivers/input/touchscreen/mc13783_ts.c index 02103b6abb39..89308fe38752 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 "); -- cgit v1.2.3 From 2b1c0d506d04aa084a88f21600888d99ce469d29 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Tue, 26 Mar 2013 21:38:47 -0700 Subject: Input: adxl34x - add CONFIG_PM_SLEEP to suspend/resume functions Add CONFIG_PM_SLEEP to suspend/resume functions to fix the following build warning when CONFIG_PM_SLEEP is not selected. This is because sleep PM callbacks defined by SIMPLE_DEV_PM_OPS are only used when the CONFIG_PM_SLEEP is enabled. drivers/input/misc/adxl34x-spi.c:98:12: warning: 'adxl34x_spi_suspend' defined but not used [-Wunused-function] drivers/input/misc/adxl34x-spi.c:108:12: warning: 'adxl34x_spi_resume' defined but not used [-Wunused-function] drivers/input/misc/adxl34x-i2c.c:109:12: warning: 'adxl34x_i2c_suspend' defined but not used [-Wunused-function] drivers/input/misc/adxl34x-i2c.c:119:12: warning: 'adxl34x_i2c_resume' defined but not used [-Wunused-function] Signed-off-by: Jingoo Han Signed-off-by: Dmitry Torokhov --- drivers/input/misc/adxl34x-i2c.c | 2 +- drivers/input/misc/adxl34x-spi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/misc/adxl34x-i2c.c b/drivers/input/misc/adxl34x-i2c.c index 535dda48cace..416f47ddcc90 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 ad5f40d37e48..990b49e712e1 100644 --- a/drivers/input/misc/adxl34x-spi.c +++ b/drivers/input/misc/adxl34x-spi.c @@ -94,7 +94,7 @@ static int adxl34x_spi_remove(struct spi_device *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); -- cgit v1.2.3 From 84c0c9f56ac7ce8036c0a70383dba1789138e859 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Tue, 26 Mar 2013 21:38:52 -0700 Subject: Input: ad714x - add CONFIG_PM_SLEEP to suspend/resume functions Add CONFIG_PM_SLEEP to suspend/resume functions to fix the following build warning when CONFIG_PM_SLEEP is not selected. This is because sleep PM callbacks defined by SIMPLE_DEV_PM_OPS are only used when the CONFIG_PM_SLEEP is enabled. drivers/input/misc/ad714x-i2c.c:17:12: warning: 'ad714x_i2c_suspend' defined but not used [-Wunused-function] drivers/input/misc/ad714x-i2c.c:22:12: warning: 'ad714x_i2c_resume' defined but not used [-Wunused-function] drivers/input/misc/ad714x-spi.c:20:12: warning: 'ad714x_spi_suspend' defined but not used [-Wunused-function] drivers/input/misc/ad714x-spi.c:25:12: warning: 'ad714x_spi_resume' defined but not used [-Wunused-function] Signed-off-by: Jingoo Han Signed-off-by: Dmitry Torokhov --- drivers/input/misc/ad714x-i2c.c | 2 +- drivers/input/misc/ad714x-spi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c index 29d2064c26f2..e0f522516ef5 100644 --- a/drivers/input/misc/ad714x-i2c.c +++ b/drivers/input/misc/ad714x-i2c.c @@ -13,7 +13,7 @@ #include #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 bdccca42d138..61891486067c 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))); -- cgit v1.2.3 From 8efcc503231dd50800f874725961dfdae5f08f52 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Thu, 28 Mar 2013 01:14:42 -0700 Subject: Input: edt-ft5x06 - remove redundant null check before kfree kfree on a null pointer is a no-op. Hence null check is not necessary. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/edt-ft5x06.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index a9170157b442..83fa1b15a97f 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 */ -- cgit v1.2.3 From 80e3e5328a9978fae3654ead657e9df653508713 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Thu, 28 Mar 2013 01:14:46 -0700 Subject: Input: eeti_ts - remove redundant null check 'pdata' is already dereferenced earlier. Hence this check is meaningless. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/eeti_ts.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c index 55255a940072..8fe5086c8d2e 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; -- cgit v1.2.3 From 257a1ec603538036a2f2ae7e8433d775b7a7588a Mon Sep 17 00:00:00 2001 From: Sebastien Royen Date: Sun, 31 Mar 2013 00:24:13 -0700 Subject: Input: mma8450 - fix signed 12bits to 32bits conversion Event value is wrong. Should be in range -2048 to 2047, but is in range 0 to 4095. Use s8 to int conversion and remove 0xfff mask. Signed-off-by: Sebastien Royen Signed-off-by: Dmitry Torokhov --- drivers/input/misc/mma8450.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/input/misc/mma8450.c b/drivers/input/misc/mma8450.c index 480557f14f23..f3309696d053 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); -- cgit v1.2.3 From 2c0a4f8b870dbeb48e3351899bd0e0cc886d4985 Mon Sep 17 00:00:00 2001 From: Mischa Jonker Date: Sun, 31 Mar 2013 00:25:33 -0700 Subject: Input: arc_ps2 - add support for device tree Add match table for device tree binding and dts binding doc. Signed-off-by: Mischa Jonker Signed-off-by: Dmitry Torokhov --- drivers/input/serio/arc_ps2.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/serio/arc_ps2.c b/drivers/input/serio/arc_ps2.c index c52e3e589f72..3fb7727c8ea5 100644 --- a/drivers/input/serio/arc_ps2.c +++ b/drivers/input/serio/arc_ps2.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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, -- cgit v1.2.3 From da5bce199fa9209c462259125f9a0144adf6885c Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sun, 31 Mar 2013 00:38:21 -0700 Subject: Input: imx_keypad - switch to using managed resources Using devm_ functions can make the code cleaner and simpler. Signed-off-by: Fabio Estevam Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/imx_keypad.c | 77 +++++++------------------------------ 1 file changed, 14 insertions(+), 63 deletions(-) (limited to 'drivers') diff --git a/drivers/input/keyboard/imx_keypad.c b/drivers/input/keyboard/imx_keypad.c index 98f9113251d2..03c8cc5cb6c1 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,60 +516,23 @@ 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; } @@ -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); -- cgit v1.2.3 From e10af9e79a97ac5d305ec1be94338e8d3fd7aaa4 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Sun, 31 Mar 2013 00:41:12 -0700 Subject: Input: tegra-kbc - support for defining row/columns based on SoC NVIDIA's Tegra20 and Tegra30 supports the 16x8 keyboard matrix and T114 support the 11x8 Key matrix. Add support for defining the maximum row/columns based on SoC through proper compatibility. Signed-off-by: Laxman Dewangan Reviewed-by: Stephen Warren Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/tegra-kbc.c | 80 +++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/input/keyboard/tegra-kbc.c b/drivers/input/keyboard/tegra-kbc.c index 68b2b65024d6..b46142f78ef2 100644 --- a/drivers/input/keyboard/tegra-kbc.c +++ b/drivers/input/keyboard/tegra-kbc.c @@ -27,18 +27,19 @@ #include #include #include +#include #include #include #include #include #include -#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 @@ -81,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; @@ -109,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, @@ -205,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]]; } } @@ -316,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); } @@ -453,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); @@ -463,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); @@ -521,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; @@ -533,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", @@ -563,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; @@ -571,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) { @@ -580,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); @@ -640,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"); @@ -766,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 = { -- cgit v1.2.3 From 3903078677a8dc6f056970b67d15840aa51e1dfa Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Mon, 1 Apr 2013 22:14:19 -0700 Subject: Input: sysrq - supplement reset sequence with timeout functionality Some devices have too few buttons, which it makes it hard to have a reset combo that won't trigger automatically. As such a timeout functionality that requires the combination to be held for a given amount of time before triggering is introduced. If a key combo is recognized and held for a 'timeout' amount of time, the system triggers a reset. If the timeout value is omitted the driver simply ignores the functionality. Signed-off-by: Mathieu Poirier Signed-off-by: Dmitry Torokhov --- drivers/tty/sysrq.c | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c index 3687f0cad642..b6b267d939a0 100644 --- a/drivers/tty/sysrq.c +++ b/drivers/tty/sysrq.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -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) -- cgit v1.2.3 From 3c2b901003b4c152e19e6578c1705ce2c90edd4a Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 1 Apr 2013 01:09:15 -0700 Subject: Input: ims-pcu - fix a memory leak on error Reported-by: Dan Carpenter Signed-off-by: Dmitry Torokhov --- drivers/input/misc/ims-pcu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c index 1b044b99da66..e204f26b0011 100644 --- a/drivers/input/misc/ims-pcu.c +++ b/drivers/input/misc/ims-pcu.c @@ -292,7 +292,8 @@ static int ims_pcu_setup_gamepad(struct ims_pcu *pcu) if (!gamepad || !input) { dev_err(pcu->dev, "Not enough memory for gamepad device\n"); - return -ENOMEM; + error = -ENOMEM; + goto err_free_mem; } gamepad->input = input; @@ -1156,7 +1157,7 @@ static ssize_t ims_pcu_update_firmware_store(struct device *dev, { struct usb_interface *intf = to_usb_interface(dev); struct ims_pcu *pcu = usb_get_intfdata(intf); - const struct firmware *fw; + const struct firmware *fw = NULL; int value; int error; -- cgit v1.2.3 From c12454fad579f3eae7260ed5c4346eeea4044ccb Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Sun, 7 Apr 2013 20:52:07 -0700 Subject: Input: ads7846 - use spi_get_drvdata() and spi_set_drvdata() Use the wrapper functions for getting and setting the driver data using spi_device instead of using dev_{get|set}_drvdata with &spi->dev, so we can directly pass a struct spi_device. Signed-off-by: Jingoo Han Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/ads7846.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c index 434c3df250ca..84ccf140c1bb 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); -- cgit v1.2.3 From e1793c69c82626717257285f437b0da7ad0d2e99 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Sun, 7 Apr 2013 20:52:12 -0700 Subject: Input: ad7877 - use spi_get_drvdata() and spi_set_drvdata() Use the wrapper functions for getting and setting the driver data using spi_device instead of using dev_{get|set}_drvdata with &spi->dev, so we can directly pass a struct spi_device. Signed-off-by: Jingoo Han Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/ad7877.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/ad7877.c b/drivers/input/touchscreen/ad7877.c index 23fa829b869d..f3a174a83c82 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; } -- cgit v1.2.3 From 7a246777a694563c490811f9d398318652a3ffb4 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Sun, 7 Apr 2013 20:52:16 -0700 Subject: Input: adxl34x - use spi_get_drvdata() Use the wrapper functions for getting and setting the driver data using spi_device instead of using dev_{get|set}_drvdata with &spi->dev, so we can directly pass a struct spi_device. Signed-off-by: Jingoo Han Signed-off-by: Dmitry Torokhov --- drivers/input/misc/adxl34x-spi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/input/misc/adxl34x-spi.c b/drivers/input/misc/adxl34x-spi.c index 990b49e712e1..76dc0679d3b1 100644 --- a/drivers/input/misc/adxl34x-spi.c +++ b/drivers/input/misc/adxl34x-spi.c @@ -89,7 +89,7 @@ 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); } @@ -98,7 +98,7 @@ static int adxl34x_spi_remove(struct spi_device *spi) 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); -- cgit v1.2.3 From a0ef6a348a47bdb3896c468608f9cd19c1c26949 Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Sun, 7 Apr 2013 20:52:22 -0700 Subject: Input: MT - handle semi-mt devices in core Most semi-mt drivers use the slots in a manual way, but really only need to treat the finger count manually. With this patch, a semi-mt driver may use the input-mt core for everything else. Signed-off-by: Henrik Rydberg Signed-off-by: Dmitry Torokhov --- drivers/input/input-mt.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c index 71db1930573f..d398f1321f14 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++; } -- cgit v1.2.3 From 95b24d22135549ac8352d719a052002838bb9f80 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 15 Apr 2013 09:23:00 -0700 Subject: Input: st1232 - convert to devm_* infrastructure Use the devm_* managed functions to allocate resources. Signed-off-by: Laurent Pinchart Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/st1232.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/st1232.c b/drivers/input/touchscreen/st1232.c index d9d05e222428..75d8eb5ee609 100644 --- a/drivers/input/touchscreen/st1232.c +++ b/drivers/input/touchscreen/st1232.c @@ -156,13 +156,13 @@ 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; @@ -179,41 +179,31 @@ 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) { - 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); return 0; } -- cgit v1.2.3 From e6a90810559cc3755a5b1629d1fd0b914462f98c Mon Sep 17 00:00:00 2001 From: Bastian Hecht Date: Mon, 15 Apr 2013 09:31:00 -0700 Subject: Input: st1232 - add reset pin handling We add the possibility to hand over a GPIO number for the reset pin. This way we can remove existing board code that takes care of it and group this information properly in the platform data or in the device tree configuration. Signed-off-by: Bastian Hecht Acked-by: Laurent Pinchart Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/st1232.c | 47 ++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/st1232.c b/drivers/input/touchscreen/st1232.c index 75d8eb5ee609..1740a2496371 100644 --- a/drivers/input/touchscreen/st1232.c +++ b/drivers/input/touchscreen/st1232.c @@ -19,13 +19,16 @@ */ #include +#include #include #include #include #include +#include #include #include #include +#include #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; @@ -167,6 +178,25 @@ static int st1232_ts_probe(struct i2c_client *client, 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; @@ -203,7 +233,10 @@ static int st1232_ts_probe(struct i2c_client *client, static int st1232_ts_remove(struct i2c_client *client) { + struct st1232_ts_data *ts = i2c_get_clientdata(client); + device_init_wakeup(&client->dev, 0); + st1232_ts_power(ts, false); return 0; } @@ -212,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; } @@ -224,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; } -- cgit v1.2.3 From 39fbe5855737b2a2b8e097373951bbe1a4fdb345 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 14 Feb 2013 09:04:24 -0800 Subject: Input: ALPS - use %ph to print buffers This form is more concise. Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/alps.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index 0238e0e14335..7c5d72a6a26a 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; } -- cgit v1.2.3 From e07a8943b809730220a4aff911d2bce6991828f1 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 15 Apr 2013 13:38:07 -0700 Subject: Input: apbps2 - convert to devm_ioremap_resource() Use the newly introduced devm_ioremap_resource() instead of devm_request_and_ioremap() which provides more consistent error handling. devm_ioremap_resource() provides its own error messages; so all explicit error messages can be removed from the failure code paths. Signed-off-by: Sachin Kamat Signed-off-by: Dmitry Torokhov --- drivers/input/serio/apbps2.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/input/serio/apbps2.c b/drivers/input/serio/apbps2.c index 2c14e6fa64c2..17e01a807ddc 100644 --- a/drivers/input/serio/apbps2.c +++ b/drivers/input/serio/apbps2.c @@ -147,11 +147,9 @@ static int apbps2_of_probe(struct platform_device *ofdev) /* Find Device Address */ res = platform_get_resource(ofdev, IORESOURCE_MEM, 0); - priv->regs = devm_request_and_ioremap(&ofdev->dev, res); - if (!priv->regs) { - dev_err(&ofdev->dev, "io-regs mapping failed\n"); - return -EBUSY; - } + 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); -- cgit v1.2.3 From 0c6a61657da78098472fd0eb71cc01f2387fa1bb Mon Sep 17 00:00:00 2001 From: Shawn Nematbakhsh Date: Mon, 15 Apr 2013 13:49:34 -0700 Subject: Input: trackpoint - Optimize trackpoint init to use power-on reset The trackpoint driver sets various parameter default values, all of which happen to be power-on defaults (Source: IBM TrackPoint Engineering Specification, Version 4.0. Also confirmed by empirical data). By sending the power-on reset command to reset all parameters to power-on state, we can skip the lengthy process of programming all parameters. In testing, ~2.5 secs of time writing parameters was reduced to .35 seconds waiting for power-on reset to complete. Signed-off-by: Shawn Nematbakhsh Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/trackpoint.c | 249 +++++++++++++++++++++++++-------------- drivers/input/mouse/trackpoint.h | 4 +- 2 files changed, 166 insertions(+), 87 deletions(-) (limited to 'drivers') diff --git a/drivers/input/mouse/trackpoint.c b/drivers/input/mouse/trackpoint.c index f3102494237d..ca843b6cf6bd 100644 --- a/drivers/input/mouse/trackpoint.c +++ b/drivers/input/mouse/trackpoint.c @@ -19,10 +19,35 @@ #include "psmouse.h" #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) - -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); - -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_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) + +#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) + +#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); - - trackpoint_write(&psmouse->ps2dev, TP_THRESH, tp->thresh); - trackpoint_write(&psmouse->ps2dev, TP_UP_THRESH, tp->upthresh); - trackpoint_write(&psmouse->ps2dev, TP_Z_TIME, tp->ztime); - trackpoint_write(&psmouse->ps2dev, TP_JENKS_CURV, tp->jenks); + 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_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_SOURCE_TAG, + TP_MASK_SOURCE_TAG, TP_DEF_SOURCE_TAG); - 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); + trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_MB, + TP_MASK_MB, TP_DEF_MB); + } - 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); + /* + * 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); + + /* 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; - - 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; + 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); + + /* 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 e558a7096618..ecd0547964a5 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; }; -- cgit v1.2.3