From 572d3c6444979a6a49c6b464110563f578e8dece Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Thu, 9 Feb 2017 18:03:57 -0800 Subject: HID: i2c-hid: support regulator power on/off On some boards, we need to enable a regulator before using the HID, and it's also nice to save power in suspend by disabling it. Support an optional "vdd-supply" and a companion initialization delay. Signed-off-by: Brian Norris Signed-off-by: Caesar Wang Acked-by: Benjamin Tissoires Reviewed-by: Dmitry Torokhov Cc: Jiri Kosina Cc: linux-input@vger.kernel.org Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) (limited to 'drivers/hid/i2c-hid/i2c-hid.c') diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index ea3c354..a3f6daf 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -994,6 +994,11 @@ static int i2c_hid_of_probe(struct i2c_client *client, } pdata->hid_descriptor_address = val; + ret = of_property_read_u32(dev->of_node, "post-power-on-delay-ms", + &val); + if (!ret) + pdata->post_power_delay_ms = val; + return 0; } @@ -1053,6 +1058,24 @@ static int i2c_hid_probe(struct i2c_client *client, ihid->pdata = *platform_data; } + ihid->pdata.supply = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(ihid->pdata.supply)) { + ret = PTR_ERR(ihid->pdata.supply); + if (ret != -EPROBE_DEFER) + dev_err(&client->dev, "Failed to get regulator: %d\n", + ret); + return ret; + } + + ret = regulator_enable(ihid->pdata.supply); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable regulator: %d\n", + ret); + goto err; + } + if (ihid->pdata.post_power_delay_ms) + msleep(ihid->pdata.post_power_delay_ms); + i2c_set_clientdata(client, ihid); ihid->client = client; @@ -1068,7 +1091,7 @@ static int i2c_hid_probe(struct i2c_client *client, * real computation later. */ ret = i2c_hid_alloc_buffers(ihid, HID_MIN_BUFFER_SIZE); if (ret < 0) - goto err; + goto err_regulator; pm_runtime_get_noresume(&client->dev); pm_runtime_set_active(&client->dev); @@ -1125,6 +1148,9 @@ err_pm: pm_runtime_put_noidle(&client->dev); pm_runtime_disable(&client->dev); +err_regulator: + regulator_disable(ihid->pdata.supply); + err: i2c_hid_free_buffers(ihid); kfree(ihid); @@ -1149,6 +1175,8 @@ static int i2c_hid_remove(struct i2c_client *client) if (ihid->bufsize) i2c_hid_free_buffers(ihid); + regulator_disable(ihid->pdata.supply); + kfree(ihid); return 0; @@ -1199,6 +1227,10 @@ static int i2c_hid_suspend(struct device *dev) else hid_warn(hid, "Failed to enable irq wake: %d\n", wake_status); + } else { + ret = regulator_disable(ihid->pdata.supply); + if (ret < 0) + hid_warn(hid, "Failed to disable supply: %d\n", ret); } return 0; @@ -1212,7 +1244,13 @@ static int i2c_hid_resume(struct device *dev) struct hid_device *hid = ihid->hid; int wake_status; - if (device_may_wakeup(&client->dev) && ihid->irq_wake_enabled) { + if (!device_may_wakeup(&client->dev)) { + ret = regulator_enable(ihid->pdata.supply); + if (ret < 0) + hid_warn(hid, "Failed to enable supply: %d\n", ret); + if (ihid->pdata.post_power_delay_ms) + msleep(ihid->pdata.post_power_delay_ms); + } else if (ihid->irq_wake_enabled) { wake_status = disable_irq_wake(client->irq); if (!wake_status) ihid->irq_wake_enabled = false; -- cgit v1.1 From d3d9adfe3059cb5cb330a2da74ea0bad49b482c0 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 19 Feb 2017 13:07:59 +0100 Subject: HID: i2c-hid: Fix error handling According to error handling in this function, it is likely that some resources should be freed before returning. Replace 'return ret', with 'goto err'. While at it, remove some spaces at the beginning of the lines to be more consistent. Fixes: ead0687fe304a ("HID: i2c-hid: support regulator power on/off") Signed-off-by: Christophe JAILLET Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hid/i2c-hid/i2c-hid.c') diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index a3f6daf..a838149 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -1064,7 +1064,7 @@ static int i2c_hid_probe(struct i2c_client *client, if (ret != -EPROBE_DEFER) dev_err(&client->dev, "Failed to get regulator: %d\n", ret); - return ret; + goto err; } ret = regulator_enable(ihid->pdata.supply); -- cgit v1.1 From 9143059fafd4eebed2d43ffb5455178d4010e60a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 8 Mar 2017 15:11:14 +0100 Subject: HID: remove initial reading of reports at connect It looks like a bunch of devices do not like to be polled for their reports at init time. When you look into the details, it seems that for those that are requiring the quirk HID_QUIRK_NO_INIT_REPORTS, the driver fails to retrieve part of the features/inputs while others (more generic) work. IMO, it should be acceptable to remove the need for the quirk in the general case. On the small amount of cases where we actually need to read the current values, the driver in charge (hid-mt or wacom) already retrieves the features manually. There are 2 cases where we might need to retrieve the reports at init: 1. hiddev devices with specific use-space tool 2. a device that would require the driver to fetch a specific feature/input at plug For case 2, I have seen this a few time on hid-multitouch. It is solved in hid-multitouch directly by fetching the feature. I hope it won't be too common and this can be solved on a per-case basis (crossing fingers). For case 1, we moved the implementation of HID_QUIRK_NO_INIT_REPORTS in hiddev. When somebody starts calling ioctls that needs an initial update, the hiddev device will fetch the initial state of the reports to mimic the current behavior. This adds a small amount of time during the first HIDIOCGUSAGE(S), but it should be acceptable in most cases. To keep the currently known broken devices, we have to keep around HID_QUIRK_NO_INIT_REPORTS, but the scope will only be for hiddev. Note that I don't think hidraw would be affected and I checked that the FF drivers that need to interact with the report fields are all using output reports, which are not initialized by usbhid_init_reports(). NO_INIT_INPUT_REPORTS is then replaced by HID_QUIRK_NO_INIT_REPORTS: there is no point keeping it for just one device. Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid.c | 63 ------------------------------------------- 1 file changed, 63 deletions(-) (limited to 'drivers/hid/i2c-hid/i2c-hid.c') diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index ea3c354..042b90d 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -508,66 +508,6 @@ static int i2c_hid_get_report_length(struct hid_report *report) report->device->report_enum[report->type].numbered + 2; } -static void i2c_hid_init_report(struct hid_report *report, u8 *buffer, - size_t bufsize) -{ - struct hid_device *hid = report->device; - struct i2c_client *client = hid->driver_data; - struct i2c_hid *ihid = i2c_get_clientdata(client); - unsigned int size, ret_size; - - size = i2c_hid_get_report_length(report); - if (i2c_hid_get_report(client, - report->type == HID_FEATURE_REPORT ? 0x03 : 0x01, - report->id, buffer, size)) - return; - - i2c_hid_dbg(ihid, "report (len=%d): %*ph\n", size, size, buffer); - - ret_size = buffer[0] | (buffer[1] << 8); - - if (ret_size != size) { - dev_err(&client->dev, "error in %s size:%d / ret_size:%d\n", - __func__, size, ret_size); - return; - } - - /* hid->driver_lock is held as we are in probe function, - * we just need to setup the input fields, so using - * hid_report_raw_event is safe. */ - hid_report_raw_event(hid, report->type, buffer + 2, size - 2, 1); -} - -/* - * Initialize all reports - */ -static void i2c_hid_init_reports(struct hid_device *hid) -{ - struct hid_report *report; - struct i2c_client *client = hid->driver_data; - struct i2c_hid *ihid = i2c_get_clientdata(client); - u8 *inbuf = kzalloc(ihid->bufsize, GFP_KERNEL); - - if (!inbuf) { - dev_err(&client->dev, "can not retrieve initial reports\n"); - return; - } - - /* - * The device must be powered on while we fetch initial reports - * from it. - */ - pm_runtime_get_sync(&client->dev); - - list_for_each_entry(report, - &hid->report_enum[HID_FEATURE_REPORT].report_list, list) - i2c_hid_init_report(report, inbuf, ihid->bufsize); - - pm_runtime_put(&client->dev); - - kfree(inbuf); -} - /* * Traverse the supplied list of reports and find the longest */ @@ -789,9 +729,6 @@ static int i2c_hid_start(struct hid_device *hid) return ret; } - if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS)) - i2c_hid_init_reports(hid); - return 0; } -- cgit v1.1