From e651a1da442ae02a50081e38309dea5e89da2d41 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 19 Feb 2015 15:31:25 -0800 Subject: HID: hid-sensor-hub: Allow parallel synchronous reads Current implementation only allows one outstanding synchronous read. This is a performance hit when user mode is requesting raw reads of sensor attributes on multiple sensors together. This change changes the mutex lock to per hid sensor hub device instead of global lock. Although request to hid sensor hub is serialized, there can be multiple outstanding read requests pending for responses via hid reports. Signed-off-by: Srinivas Pandruvada Acked-by: Jonathan Cameron Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 6a58b6c..62fadf8 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -29,29 +29,10 @@ #define HID_SENSOR_HUB_ENUM_QUIRK 0x01 /** - * struct sensor_hub_pending - Synchronous read pending information - * @status: Pending status true/false. - * @ready: Completion synchronization data. - * @usage_id: Usage id for physical device, E.g. Gyro usage id. - * @attr_usage_id: Usage Id of a field, E.g. X-AXIS for a gyro. - * @raw_size: Response size for a read request. - * @raw_data: Place holder for received response. - */ -struct sensor_hub_pending { - bool status; - struct completion ready; - u32 usage_id; - u32 attr_usage_id; - int raw_size; - u8 *raw_data; -}; - -/** * struct sensor_hub_data - Hold a instance data for a HID hub device * @hsdev: Stored hid instance for current hub device. * @mutex: Mutex to serialize synchronous request. * @lock: Spin lock to protect pending request structure. - * @pending: Holds information of pending sync read request. * @dyn_callback_list: Holds callback function * @dyn_callback_lock: spin lock to protect callback list * @hid_sensor_hub_client_devs: Stores all MFD cells for a hub instance. @@ -61,7 +42,6 @@ struct sensor_hub_pending { struct sensor_hub_data { struct mutex mutex; spinlock_t lock; - struct sensor_hub_pending pending; struct list_head dyn_callback_list; spinlock_t dyn_callback_lock; struct mfd_cell *hid_sensor_hub_client_devs; @@ -264,40 +244,42 @@ int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, struct hid_report *report; int ret_val = 0; - mutex_lock(&data->mutex); - memset(&data->pending, 0, sizeof(data->pending)); - init_completion(&data->pending.ready); - data->pending.usage_id = usage_id; - data->pending.attr_usage_id = attr_usage_id; - data->pending.raw_size = 0; + mutex_lock(&hsdev->mutex); + memset(&hsdev->pending, 0, sizeof(hsdev->pending)); + init_completion(&hsdev->pending.ready); + hsdev->pending.usage_id = usage_id; + hsdev->pending.attr_usage_id = attr_usage_id; + hsdev->pending.raw_size = 0; spin_lock_irqsave(&data->lock, flags); - data->pending.status = true; + hsdev->pending.status = true; spin_unlock_irqrestore(&data->lock, flags); report = sensor_hub_report(report_id, hsdev->hdev, HID_INPUT_REPORT); if (!report) goto err_free; + mutex_lock(&data->mutex); hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); - wait_for_completion_interruptible_timeout(&data->pending.ready, HZ*5); - switch (data->pending.raw_size) { + mutex_unlock(&data->mutex); + wait_for_completion_interruptible_timeout(&hsdev->pending.ready, HZ*5); + switch (hsdev->pending.raw_size) { case 1: - ret_val = *(u8 *)data->pending.raw_data; + ret_val = *(u8 *)hsdev->pending.raw_data; break; case 2: - ret_val = *(u16 *)data->pending.raw_data; + ret_val = *(u16 *)hsdev->pending.raw_data; break; case 4: - ret_val = *(u32 *)data->pending.raw_data; + ret_val = *(u32 *)hsdev->pending.raw_data; break; default: ret_val = 0; } - kfree(data->pending.raw_data); + kfree(hsdev->pending.raw_data); err_free: - data->pending.status = false; - mutex_unlock(&data->mutex); + hsdev->pending.status = false; + mutex_unlock(&hsdev->mutex); return ret_val; } @@ -453,16 +435,6 @@ static int sensor_hub_raw_event(struct hid_device *hdev, report->field[i]->report_count)/8); sz = (report->field[i]->report_size * report->field[i]->report_count)/8; - if (pdata->pending.status && pdata->pending.attr_usage_id == - report->field[i]->usage->hid) { - hid_dbg(hdev, "data was pending ...\n"); - pdata->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); - if (pdata->pending.raw_data) - pdata->pending.raw_size = sz; - else - pdata->pending.raw_size = 0; - complete(&pdata->pending.ready); - } collection = &hdev->collection[ report->field[i]->usage->collection_index]; hid_dbg(hdev, "collection->usage %x\n", @@ -472,8 +444,21 @@ static int sensor_hub_raw_event(struct hid_device *hdev, report->field[i]->physical, report->field[i]->usage[0].collection_index, &hsdev, &priv); - - if (callback && callback->capture_sample) { + if (!callback) { + ptr += sz; + continue; + } + if (hsdev->pending.status && hsdev->pending.attr_usage_id == + report->field[i]->usage->hid) { + hid_dbg(hdev, "data was pending ...\n"); + hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); + if (hsdev->pending.raw_data) + hsdev->pending.raw_size = sz; + else + hsdev->pending.raw_size = 0; + complete(&hsdev->pending.ready); + } + if (callback->capture_sample) { if (report->field[i]->logical) callback->capture_sample(hsdev, report->field[i]->logical, sz, ptr, @@ -628,6 +613,8 @@ static int sensor_hub_probe(struct hid_device *hdev, hsdev->hdev = hdev; hsdev->vendor_id = hdev->vendor; hsdev->product_id = hdev->product; + hsdev->usage = collection->usage; + mutex_init(&hsdev->mutex); hsdev->start_collection_index = i; if (last_hsdev) last_hsdev->end_collection_index = i; @@ -674,13 +661,18 @@ static void sensor_hub_remove(struct hid_device *hdev) { struct sensor_hub_data *data = hid_get_drvdata(hdev); unsigned long flags; + int i; hid_dbg(hdev, " hardware removed\n"); hid_hw_close(hdev); hid_hw_stop(hdev); spin_lock_irqsave(&data->lock, flags); - if (data->pending.status) - complete(&data->pending.ready); + for (i = 0; i < data->hid_sensor_client_cnt; ++i) { + struct hid_sensor_hub_device *hsdev = + data->hid_sensor_hub_client_devs[i].platform_data; + if (hsdev->pending.status) + complete(&hsdev->pending.ready); + } spin_unlock_irqrestore(&data->lock, flags); mfd_remove_devices(&hdev->dev); hid_set_drvdata(hdev, NULL); diff --git a/include/linux/hid-sensor-hub.h b/include/linux/hid-sensor-hub.h index 51f7cca..60a34277dd 100644 --- a/include/linux/hid-sensor-hub.h +++ b/include/linux/hid-sensor-hub.h @@ -47,19 +47,43 @@ struct hid_sensor_hub_attribute_info { }; /** + * struct sensor_hub_pending - Synchronous read pending information + * @status: Pending status true/false. + * @ready: Completion synchronization data. + * @usage_id: Usage id for physical device, E.g. Gyro usage id. + * @attr_usage_id: Usage Id of a field, E.g. X-AXIS for a gyro. + * @raw_size: Response size for a read request. + * @raw_data: Place holder for received response. + */ +struct sensor_hub_pending { + bool status; + struct completion ready; + u32 usage_id; + u32 attr_usage_id; + int raw_size; + u8 *raw_data; +}; + +/** * struct hid_sensor_hub_device - Stores the hub instance data * @hdev: Stores the hid instance. * @vendor_id: Vendor id of hub device. * @product_id: Product id of hub device. + * @usage: Usage id for this hub device instance. * @start_collection_index: Starting index for a phy type collection * @end_collection_index: Last index for a phy type collection + * @mutex: synchronizing mutex. + * @pending: Holds information of pending sync read request. */ struct hid_sensor_hub_device { struct hid_device *hdev; u32 vendor_id; u32 product_id; + u32 usage; int start_collection_index; int end_collection_index; + struct mutex mutex; + struct sensor_hub_pending pending; }; /** -- cgit v0.10.2 From cb67126f32f008b9abe97fbfca9b23a797b2458a Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 19 Feb 2015 15:31:26 -0800 Subject: HID: hid-sensor-hub: Add support for application collection Section 4.2.5 of HID Sensor hub specification allows two methods defining sensor devices. - Each sensor device by its own collection - A top level application collection object, including multiple sensors. In the first method, each sensor can be in its own sensor application collection without a physical collection. In the second method there is a usage id for collection type, which is defined as an application collection, with multiple physical collections in it. It is possible to define fusion sensor with this and may have its own handler. If there is a callback registered for the collection type, then forward all reports for sensors in its collection to this handler. Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 62fadf8..c325f85 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -86,7 +86,8 @@ static int sensor_hub_get_physical_device_count(struct hid_device *hdev) for (i = 0; i < hdev->maxcollection; ++i) { struct hid_collection *collection = &hdev->collection[i]; - if (collection->type == HID_COLLECTION_PHYSICAL) + if (collection->type == HID_COLLECTION_PHYSICAL || + collection->type == HID_COLLECTION_APPLICATION) ++count; } @@ -118,7 +119,8 @@ static struct hid_sensor_hub_callbacks *sensor_hub_get_callback( spin_lock(&pdata->dyn_callback_lock); list_for_each_entry(callback, &pdata->dyn_callback_list, list) - if (callback->usage_id == usage_id && + if ((callback->usage_id == usage_id || + callback->usage_id == HID_USAGE_SENSOR_COLLECTION) && (collection_index >= callback->hsdev->start_collection_index) && (collection_index < @@ -157,7 +159,18 @@ int sensor_hub_register_callback(struct hid_sensor_hub_device *hsdev, callback->usage_callback = usage_callback; callback->usage_id = usage_id; callback->priv = NULL; - list_add_tail(&callback->list, &pdata->dyn_callback_list); + /* + * If there is a handler registered for the collection type, then + * it will handle all reports for sensors in this collection. If + * there is also an individual sensor handler registration, then + * we want to make sure that the reports are directed to collection + * handler, as this may be a fusion sensor. So add collection handlers + * to the beginning of the list, so that they are matched first. + */ + if (usage_id == HID_USAGE_SENSOR_COLLECTION) + list_add(&callback->list, &pdata->dyn_callback_list); + else + list_add_tail(&callback->list, &pdata->dyn_callback_list); spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); return 0; @@ -555,6 +568,7 @@ static int sensor_hub_probe(struct hid_device *hdev, int dev_cnt; struct hid_sensor_hub_device *hsdev; struct hid_sensor_hub_device *last_hsdev = NULL; + struct hid_sensor_hub_device *collection_hsdev = NULL; sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); if (!sd) { @@ -601,7 +615,8 @@ static int sensor_hub_probe(struct hid_device *hdev, for (i = 0; i < hdev->maxcollection; ++i) { struct hid_collection *collection = &hdev->collection[i]; - if (collection->type == HID_COLLECTION_PHYSICAL) { + if (collection->type == HID_COLLECTION_PHYSICAL || + collection->type == HID_COLLECTION_APPLICATION) { hsdev = devm_kzalloc(&hdev->dev, sizeof(*hsdev), GFP_KERNEL); @@ -638,10 +653,17 @@ static int sensor_hub_probe(struct hid_device *hdev, hid_dbg(hdev, "Adding %s:%d\n", name, hsdev->start_collection_index); sd->hid_sensor_client_cnt++; + if (collection_hsdev) + collection_hsdev->end_collection_index = i; + if (collection->type == HID_COLLECTION_APPLICATION && + collection->usage == HID_USAGE_SENSOR_COLLECTION) + collection_hsdev = hsdev; } } if (last_hsdev) last_hsdev->end_collection_index = i; + if (collection_hsdev) + collection_hsdev->end_collection_index = i; ret = mfd_add_hotplug_devices(&hdev->dev, sd->hid_sensor_hub_client_devs, diff --git a/include/linux/hid-sensor-ids.h b/include/linux/hid-sensor-ids.h index 109f0e6..f2ee90a 100644 --- a/include/linux/hid-sensor-ids.h +++ b/include/linux/hid-sensor-ids.h @@ -21,6 +21,8 @@ #define HID_MAX_PHY_DEVICES 0xFF +#define HID_USAGE_SENSOR_COLLECTION 0x200001 + /* Accel 3D (200073) */ #define HID_USAGE_SENSOR_ACCEL_3D 0x200073 #define HID_USAGE_SENSOR_DATA_ACCELERATION 0x200452 -- cgit v0.10.2 From b3f4737d00de317d1549d5cb5b1dad90e19f5cec Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 19 Feb 2015 15:33:56 -0800 Subject: HID: hid-sensor-hub: Extend API for async reads Add additional flag to read in async mode. In this mode the caller will get reply via registered callback for capture_sample. Callbacks can be registered using sensor_hub_register_callback function. The usage id parameter of the capture_sample can be matched with the usage id of the requested attribute. Signed-off-by: Srinivas Pandruvada Acked-by: Jonathan Cameron Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index c325f85..0a91623 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -250,48 +250,53 @@ EXPORT_SYMBOL_GPL(sensor_hub_get_feature); int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, u32 usage_id, - u32 attr_usage_id, u32 report_id) + u32 attr_usage_id, u32 report_id, + enum sensor_hub_read_flags flag) { struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); unsigned long flags; struct hid_report *report; int ret_val = 0; - mutex_lock(&hsdev->mutex); - memset(&hsdev->pending, 0, sizeof(hsdev->pending)); - init_completion(&hsdev->pending.ready); - hsdev->pending.usage_id = usage_id; - hsdev->pending.attr_usage_id = attr_usage_id; - hsdev->pending.raw_size = 0; - - spin_lock_irqsave(&data->lock, flags); - hsdev->pending.status = true; - spin_unlock_irqrestore(&data->lock, flags); - report = sensor_hub_report(report_id, hsdev->hdev, HID_INPUT_REPORT); + report = sensor_hub_report(report_id, hsdev->hdev, + HID_INPUT_REPORT); if (!report) - goto err_free; + return -EINVAL; + mutex_lock(&hsdev->mutex); + if (flag == SENSOR_HUB_SYNC) { + memset(&hsdev->pending, 0, sizeof(hsdev->pending)); + init_completion(&hsdev->pending.ready); + hsdev->pending.usage_id = usage_id; + hsdev->pending.attr_usage_id = attr_usage_id; + hsdev->pending.raw_size = 0; + + spin_lock_irqsave(&data->lock, flags); + hsdev->pending.status = true; + spin_unlock_irqrestore(&data->lock, flags); + } mutex_lock(&data->mutex); hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); mutex_unlock(&data->mutex); - wait_for_completion_interruptible_timeout(&hsdev->pending.ready, HZ*5); - switch (hsdev->pending.raw_size) { - case 1: - ret_val = *(u8 *)hsdev->pending.raw_data; - break; - case 2: - ret_val = *(u16 *)hsdev->pending.raw_data; - break; - case 4: - ret_val = *(u32 *)hsdev->pending.raw_data; - break; - default: - ret_val = 0; + if (flag == SENSOR_HUB_SYNC) { + wait_for_completion_interruptible_timeout( + &hsdev->pending.ready, HZ*5); + switch (hsdev->pending.raw_size) { + case 1: + ret_val = *(u8 *)hsdev->pending.raw_data; + break; + case 2: + ret_val = *(u16 *)hsdev->pending.raw_data; + break; + case 4: + ret_val = *(u32 *)hsdev->pending.raw_data; + break; + default: + ret_val = 0; + } + kfree(hsdev->pending.raw_data); + hsdev->pending.status = false; } - kfree(hsdev->pending.raw_data); - -err_free: - hsdev->pending.status = false; mutex_unlock(&hsdev->mutex); return ret_val; diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c index d5d95317..0085c2f 100644 --- a/drivers/iio/accel/hid-sensor-accel-3d.c +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -130,7 +130,8 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( accel_state->common_attributes.hsdev, HID_USAGE_SENSOR_ACCEL_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { *val = 0; hid_sensor_power_state(&accel_state->common_attributes, diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c index a3ea1e8..bdcd105 100644 --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -130,7 +130,8 @@ static int gyro_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( gyro_state->common_attributes.hsdev, HID_USAGE_SENSOR_GYRO_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { *val = 0; hid_sensor_power_state(&gyro_state->common_attributes, diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index a5283d7..321862d 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -109,7 +109,8 @@ static int als_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( als_state->common_attributes.hsdev, HID_USAGE_SENSOR_ALS, address, - report_id); + report_id, + SENSOR_HUB_SYNC); hid_sensor_power_state(&als_state->common_attributes, false); } else { diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c index f5a5146..db9c60e 100644 --- a/drivers/iio/light/hid-sensor-prox.c +++ b/drivers/iio/light/hid-sensor-prox.c @@ -105,7 +105,8 @@ static int prox_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( prox_state->common_attributes.hsdev, HID_USAGE_SENSOR_PROX, address, - report_id); + report_id, + SENSOR_HUB_SYNC); hid_sensor_power_state(&prox_state->common_attributes, false); } else { diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c index 6294575..4d299f3 100644 --- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c @@ -178,7 +178,8 @@ static int magn_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( magn_state->common_attributes.hsdev, HID_USAGE_SENSOR_COMPASS_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { *val = 0; hid_sensor_power_state(&magn_state->common_attributes, diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c index 1ff181b..45bed08 100644 --- a/drivers/iio/orientation/hid-sensor-incl-3d.c +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c @@ -132,7 +132,8 @@ static int incl_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( incl_state->common_attributes.hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { hid_sensor_power_state(&incl_state->common_attributes, false); diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c index 7649286..ac150f0 100644 --- a/drivers/iio/pressure/hid-sensor-press.c +++ b/drivers/iio/pressure/hid-sensor-press.c @@ -108,7 +108,8 @@ static int press_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( press_state->common_attributes.hsdev, HID_USAGE_SENSOR_PRESSURE, address, - report_id); + report_id, + SENSOR_HUB_SYNC); hid_sensor_power_state(&press_state->common_attributes, false); } else { diff --git a/drivers/rtc/rtc-hid-sensor-time.c b/drivers/rtc/rtc-hid-sensor-time.c index ae7c2ba..af4f85a 100644 --- a/drivers/rtc/rtc-hid-sensor-time.c +++ b/drivers/rtc/rtc-hid-sensor-time.c @@ -213,7 +213,7 @@ static int hid_rtc_read_time(struct device *dev, struct rtc_time *tm) /* get a report with all values through requesting one value */ sensor_hub_input_attr_get_raw_value(time_state->common_attributes.hsdev, HID_USAGE_SENSOR_TIME, hid_time_addresses[0], - time_state->info[0].report_id); + time_state->info[0].report_id, SENSOR_HUB_SYNC); /* wait for all values (event) */ ret = wait_for_completion_killable_timeout( &time_state->comp_last_time, HZ*6); diff --git a/include/linux/hid-sensor-hub.h b/include/linux/hid-sensor-hub.h index 60a34277dd..4c49b04 100644 --- a/include/linux/hid-sensor-hub.h +++ b/include/linux/hid-sensor-hub.h @@ -169,19 +169,27 @@ int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev, struct hid_sensor_hub_attribute_info *info); /** -* sensor_hub_input_attr_get_raw_value() - Synchronous read request +* sensor_hub_input_attr_get_raw_value() - Attribute read request * @usage_id: Attribute usage id of parent physical device as per spec * @attr_usage_id: Attribute usage id as per spec * @report_id: Report id to look for +* @flag: Synchronous or asynchronous read * -* Issues a synchronous read request for an input attribute. Returns -* data upto 32 bits. Since client can get events, so this call should -* not be used for data paths, this will impact performance. +* Issues a synchronous or asynchronous read request for an input attribute. +* Returns data upto 32 bits. */ +enum sensor_hub_read_flags { + SENSOR_HUB_SYNC, + SENSOR_HUB_ASYNC, +}; + int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, - u32 usage_id, - u32 attr_usage_id, u32 report_id); + u32 usage_id, + u32 attr_usage_id, u32 report_id, + enum sensor_hub_read_flags flag +); + /** * sensor_hub_set_feature() - Feature set request * @report_id: Report id to look for -- cgit v0.10.2 From 6adc83fca74ab73abcbd3b394cf3a8fd3701db99 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 19 Feb 2015 15:35:25 -0800 Subject: HID: hid-sensor-hub: Enhance get feature report API Some hid sensor feature report can contain more than one reports. This API can now support receiving multiple values from the feature report. Also update the parameters in the users of this API. Signed-off-by: Srinivas Pandruvada Acked-by: Jonathan Cameron Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 0a91623..c025c48 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -223,10 +223,11 @@ done_proc: EXPORT_SYMBOL_GPL(sensor_hub_set_feature); int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 *value) + u32 field_index, int buffer_size, void *buffer) { struct hid_report *report; struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + int report_size; int ret = 0; mutex_lock(&data->mutex); @@ -238,7 +239,17 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, } hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); hid_hw_wait(hsdev->hdev); - *value = report->field[field_index]->value[0]; + + /* calculate number of bytes required to read this field */ + report_size = DIV_ROUND_UP(report->field[field_index]->report_size, + 8) * + report->field[field_index]->report_count; + if (!report_size) { + ret = -EINVAL; + goto done_proc; + } + ret = min(report_size, buffer_size); + memcpy(buffer, report->field[field_index]->value, ret); done_proc: mutex_unlock(&data->mutex); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c index 25b01e1..e1435e9 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -153,8 +153,8 @@ s32 hid_sensor_read_poll_value(struct hid_sensor_common *st) int ret; ret = sensor_hub_get_feature(st->hsdev, - st->poll.report_id, - st->poll.index, &value); + st->poll.report_id, + st->poll.index, sizeof(value), &value); if (ret < 0 || value < 0) { return -EINVAL; @@ -174,8 +174,8 @@ int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st, int ret; ret = sensor_hub_get_feature(st->hsdev, - st->poll.report_id, - st->poll.index, &value); + st->poll.report_id, + st->poll.index, sizeof(value), &value); if (ret < 0 || value < 0) { *val1 = *val2 = 0; return -EINVAL; @@ -229,8 +229,9 @@ int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st, int ret; ret = sensor_hub_get_feature(st->hsdev, - st->sensitivity.report_id, - st->sensitivity.index, &value); + st->sensitivity.report_id, + st->sensitivity.index, sizeof(value), + &value); if (ret < 0 || value < 0) { *val1 = *val2 = 0; return -EINVAL; diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index 92068cd..ef0c495 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -76,8 +76,8 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state) } sensor_hub_get_feature(st->hsdev, st->power_state.report_id, - st->power_state.index, - &state_val); + st->power_state.index, + sizeof(state_val), &state_val); return 0; } EXPORT_SYMBOL(hid_sensor_power_state); diff --git a/include/linux/hid-sensor-hub.h b/include/linux/hid-sensor-hub.h index 4c49b04..1db3320 100644 --- a/include/linux/hid-sensor-hub.h +++ b/include/linux/hid-sensor-hub.h @@ -206,13 +206,15 @@ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, * sensor_hub_get_feature() - Feature get request * @report_id: Report id to look for * @field_index: Field index inside a report -* @value: Place holder for return value +* @buffer_size: size of the buffer +* @buffer: buffer to copy output * * Used to get a field in feature report. For example this can get polling -* interval, sensitivity, activate/deactivate state. +* interval, sensitivity, activate/deactivate state. On success it returns +* number of bytes copied to buffer. On failure, it returns value < 0. */ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 *value); + u32 field_index, int buffer_size, void *buffer); /* hid-sensor-attributes */ -- cgit v0.10.2 From 3950e03389cfc8ee9d7131074d999b5fb6bbc2bf Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 19 Feb 2015 15:35:26 -0800 Subject: HID: hid-sensor-hub: Enhance feature report set API Current API only allows setting one offset in the field. This API is extended to set multiple offsets in the field report. Also update parameters in the users of this API. Signed-off-by: Srinivas Pandruvada Reviewed-by: Jonathan Cameron Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index c025c48..44da796 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -199,10 +199,14 @@ int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev, EXPORT_SYMBOL_GPL(sensor_hub_remove_callback); int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 value) + u32 field_index, int buffer_size, void *buffer) { struct hid_report *report; struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + __s32 *buf32 = buffer; + int i = 0; + int remaining_bytes; + __s32 value; int ret = 0; mutex_lock(&data->mutex); @@ -211,7 +215,21 @@ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, ret = -EINVAL; goto done_proc; } - hid_set_field(report->field[field_index], 0, value); + + remaining_bytes = do_div(buffer_size, sizeof(__s32)); + if (buffer_size) { + for (i = 0; i < buffer_size; ++i) { + hid_set_field(report->field[field_index], i, + cpu_to_le32(*buf32)); + ++buf32; + } + } + if (remaining_bytes) { + value = 0; + memcpy(&value, (u8 *)buf32, remaining_bytes); + hid_set_field(report->field[field_index], i, + cpu_to_le32(value)); + } hid_hw_request(hsdev->hdev, report, HID_REQ_SET_REPORT); hid_hw_wait(hsdev->hdev); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c index e1435e9..e81f434 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -212,9 +212,8 @@ int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st, else value = 0; } - ret = sensor_hub_set_feature(st->hsdev, - st->poll.report_id, - st->poll.index, value); + ret = sensor_hub_set_feature(st->hsdev, st->poll.report_id, + st->poll.index, sizeof(value), &value); if (ret < 0 || value < 0) ret = -EINVAL; @@ -254,9 +253,9 @@ int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st, value = convert_to_vtf_format(st->sensitivity.size, st->sensitivity.unit_expo, val1, val2); - ret = sensor_hub_set_feature(st->hsdev, - st->sensitivity.report_id, - st->sensitivity.index, value); + ret = sensor_hub_set_feature(st->hsdev, st->sensitivity.report_id, + st->sensitivity.index, sizeof(value), + &value); if (ret < 0 || value < 0) ret = -EINVAL; diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index ef0c495..910e82a 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -64,15 +64,16 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state) if (state_val >= 0) { state_val += st->power_state.logical_minimum; sensor_hub_set_feature(st->hsdev, st->power_state.report_id, - st->power_state.index, - (s32)state_val); + st->power_state.index, sizeof(state_val), + &state_val); } if (report_val >= 0) { report_val += st->report_state.logical_minimum; sensor_hub_set_feature(st->hsdev, st->report_state.report_id, - st->report_state.index, - (s32)report_val); + st->report_state.index, + sizeof(report_val), + &report_val); } sensor_hub_get_feature(st->hsdev, st->power_state.report_id, diff --git a/include/linux/hid-sensor-hub.h b/include/linux/hid-sensor-hub.h index 1db3320..4a2fdba 100644 --- a/include/linux/hid-sensor-hub.h +++ b/include/linux/hid-sensor-hub.h @@ -194,13 +194,14 @@ int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, * sensor_hub_set_feature() - Feature set request * @report_id: Report id to look for * @field_index: Field index inside a report -* @value: Value to set +* @buffer_size: size of the buffer +* @buffer: buffer to use in the feature set * * Used to set a field in feature report. For example this can set polling * interval, sensitivity, activate/deactivate state. */ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 value); + u32 field_index, int buffer_size, void *buffer); /** * sensor_hub_get_feature() - Feature get request -- cgit v0.10.2 From eb96483349e946f10d41c2a5674922e1d86603d9 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 25 Feb 2015 18:56:27 -0800 Subject: HID: hid-sensor-hub: fix attribute read for logical usage id For defining enumeration values like report or power status events, the enumeration usage ids are enclosed in a logical collection. In this case we need to match logical usage id for pending read on this usage id. For example, in the below field, when read is requested for 0319, the report will contain one of the status usages like 850, 851 etc. In this case the raw event will not match 0319. So when logical collection matches, then wake up the pending thread. Physical(Sensor.OtherCustom) Logical(Sensor.0319) Application(Sensor.Sensor) Usage(6) Sensor.0850 Sensor.0851 Sensor.0852 Sensor.0853 Sensor.0854 Sensor.0855 Logical Minimum(1) Logical Maximum(5) Report Size(8) Report Count(1) Report Offset(24) Flags( Array Absolute ) Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 44da796..96f18be 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -495,8 +495,10 @@ static int sensor_hub_raw_event(struct hid_device *hdev, ptr += sz; continue; } - if (hsdev->pending.status && hsdev->pending.attr_usage_id == - report->field[i]->usage->hid) { + if (hsdev->pending.status && (hsdev->pending.attr_usage_id == + report->field[i]->usage->hid || + hsdev->pending.attr_usage_id == + report->field[i]->logical)) { hid_dbg(hdev, "data was pending ...\n"); hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); if (hsdev->pending.raw_data) -- cgit v0.10.2 From 62fad137bb1fc1dd08352aba18e7e13d0d682ef3 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 26 Feb 2015 11:04:44 -0800 Subject: HID: hid-sensor-hub: Fix sparse warning Since I can't change the type of hid_set_field argument 3, using __force __s32 to remove this warning. Reported-by: kbuild test robot Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 96f18be..ecdcb5a 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -220,7 +220,7 @@ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, if (buffer_size) { for (i = 0; i < buffer_size; ++i) { hid_set_field(report->field[field_index], i, - cpu_to_le32(*buf32)); + (__force __s32)cpu_to_le32(*buf32)); ++buf32; } } @@ -228,7 +228,7 @@ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, value = 0; memcpy(&value, (u8 *)buf32, remaining_bytes); hid_set_field(report->field[field_index], i, - cpu_to_le32(value)); + (__force __s32)cpu_to_le32(value)); } hid_hw_request(hsdev->hdev, report, HID_REQ_SET_REPORT); hid_hw_wait(hsdev->hdev); -- cgit v0.10.2 From 4a7de0519df5e8fb89cef6ee062330ffe4b50a4d Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Fri, 10 Apr 2015 11:22:22 -0700 Subject: HID: sensor: Custom and Generic sensor support HID Sensor Spec defines two usage ids for custom sensors HID_USAGE_SENSOR_TYPE_OTHER_CUSTOM (0x09, 0xE1) HID_USAGE_SENSOR_TYPE_OTHER_GENERIC(0x09, 0xE2) In addition the standard also defines usage ids for custom fields. The purpose of these sensors is to extend the functionality or provide a way to obfuscate the data being communicated by a sensor. Without knowing the mapping between the data and its encapsulated form, it is difficult for an driver to determine what data is being communicated by the sensor. This allows some differentiating use cases, where vendor can provide applications. Since these can't be represented by standard sensor interfaces like IIO, we present these as fields with - type (input/output) - units - min/max - get/set value In addition an dev interface to transfer report events. Details about this interface is described in /Documentation/hid/hid-sensor.txt. Manufacturers should not use these ids for any standard sensors, otherwise the the product/vendor id can be added to black list. Signed-off-by: Srinivas Pandruvada Reviewed-by: Jonathan Cameron Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 152b006..7de0e9e 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -885,6 +885,21 @@ config HID_SENSOR_HUB for events and handle data streams. Each sensor driver can format data and present to user mode using input or IIO interface. +config HID_SENSOR_CUSTOM_SENSOR + tristate "HID Sensors hub custom sensor support" + depends on HID_SENSOR_HUB + default n + ---help--- + HID Sensor hub specification allows definition of some custom and + generic sensors. Unlike other HID sensors, they can't be exported + via Linux IIO because of custom fields. This is up to the manufacturer + to decide how to interpret these special sensor ids and process in + the user space. Currently some manufacturers are using these ids for + sensor calibration and debugging other sensors. Manufacturers + should't use these special custom sensor ids to export any of the + standard sensors. + Select this config option for custom/generic sensor support. + endmenu endif # HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6f19958..c90ce7b 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -101,6 +101,7 @@ obj-$(CONFIG_HID_WACOM) += wacom.o obj-$(CONFIG_HID_WALTOP) += hid-waltop.o obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o +obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o obj-$(CONFIG_USB_HID) += usbhid/ obj-$(CONFIG_USB_MOUSE) += usbhid/ diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c new file mode 100644 index 0000000..5614fee --- /dev/null +++ b/drivers/hid/hid-sensor-custom.c @@ -0,0 +1,849 @@ +/* + * hid-sensor-custom.c + * Copyright (c) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HID_CUSTOM_NAME_LENGTH 64 +#define HID_CUSTOM_MAX_CORE_ATTRS 10 +#define HID_CUSTOM_TOTAL_ATTRS (HID_CUSTOM_MAX_CORE_ATTRS + 1) +#define HID_CUSTOM_FIFO_SIZE 4096 +#define HID_CUSTOM_MAX_FEATURE_BYTES 64 + +struct hid_sensor_custom_field { + int report_id; + char group_name[HID_CUSTOM_NAME_LENGTH]; + struct hid_sensor_hub_attribute_info attribute; + struct device_attribute sd_attrs[HID_CUSTOM_MAX_CORE_ATTRS]; + char attr_name[HID_CUSTOM_TOTAL_ATTRS][HID_CUSTOM_NAME_LENGTH]; + struct attribute *attrs[HID_CUSTOM_TOTAL_ATTRS]; + struct attribute_group hid_custom_attribute_group; +}; + +struct hid_sensor_custom { + struct mutex mutex; + struct platform_device *pdev; + struct hid_sensor_hub_device *hsdev; + struct hid_sensor_hub_callbacks callbacks; + int sensor_field_count; + struct hid_sensor_custom_field *fields; + int input_field_count; + int input_report_size; + int input_report_recd_size; + bool input_skip_sample; + bool enable; + struct hid_sensor_custom_field *power_state; + struct hid_sensor_custom_field *report_state; + struct miscdevice custom_dev; + struct kfifo data_fifo; + unsigned long misc_opened; + wait_queue_head_t wait; +}; + +/* Header for each sample to user space via dev interface */ +struct hid_sensor_sample { + u32 usage_id; + u64 timestamp; + u32 raw_len; +} __packed; + +static struct attribute hid_custom_attrs[] = { + {.name = "name", .mode = S_IRUGO}, + {.name = "units", .mode = S_IRUGO}, + {.name = "unit-expo", .mode = S_IRUGO}, + {.name = "minimum", .mode = S_IRUGO}, + {.name = "maximum", .mode = S_IRUGO}, + {.name = "size", .mode = S_IRUGO}, + {.name = "value", .mode = S_IWUSR | S_IRUGO}, + {.name = NULL} +}; + +static const struct hid_custom_usage_desc { + int usage_id; + char *desc; +} hid_custom_usage_desc_table[] = { + {0x200201, "event-sensor-state"}, + {0x200202, "event-sensor-event"}, + {0x200301, "property-friendly-name"}, + {0x200302, "property-persistent-unique-id"}, + {0x200303, "property-sensor-status"}, + {0x200304, "property-min-report-interval"}, + {0x200305, "property-sensor-manufacturer"}, + {0x200306, "property-sensor-model"}, + {0x200307, "property-sensor-serial-number"}, + {0x200308, "property-sensor-description"}, + {0x200309, "property-sensor-connection-type"}, + {0x20030A, "property-sensor-device-path"}, + {0x20030B, "property-hardware-revision"}, + {0x20030C, "property-firmware-version"}, + {0x20030D, "property-release-date"}, + {0x20030E, "property-report-interval"}, + {0x20030F, "property-change-sensitivity-absolute"}, + {0x200310, "property-change-sensitivity-percent-range"}, + {0x200311, "property-change-sensitivity-percent-relative"}, + {0x200312, "property-accuracy"}, + {0x200313, "property-resolution"}, + {0x200314, "property-maximum"}, + {0x200315, "property-minimum"}, + {0x200316, "property-reporting-state"}, + {0x200317, "property-sampling-rate"}, + {0x200318, "property-response-curve"}, + {0x200319, "property-power-state"}, + {0x200540, "data-field-custom"}, + {0x200541, "data-field-custom-usage"}, + {0x200542, "data-field-custom-boolean-array"}, + {0x200543, "data-field-custom-value"}, + {0x200544, "data-field-custom-value_1"}, + {0x200545, "data-field-custom-value_2"}, + {0x200546, "data-field-custom-value_3"}, + {0x200547, "data-field-custom-value_4"}, + {0x200548, "data-field-custom-value_5"}, + {0x200549, "data-field-custom-value_6"}, + {0x20054A, "data-field-custom-value_7"}, + {0x20054B, "data-field-custom-value_8"}, + {0x20054C, "data-field-custom-value_9"}, + {0x20054D, "data-field-custom-value_10"}, + {0x20054E, "data-field-custom-value_11"}, + {0x20054F, "data-field-custom-value_12"}, + {0x200550, "data-field-custom-value_13"}, + {0x200551, "data-field-custom-value_14"}, + {0x200552, "data-field-custom-value_15"}, + {0x200553, "data-field-custom-value_16"}, + {0x200554, "data-field-custom-value_17"}, + {0x200555, "data-field-custom-value_18"}, + {0x200556, "data-field-custom-value_19"}, + {0x200557, "data-field-custom-value_20"}, + {0x200558, "data-field-custom-value_21"}, + {0x200559, "data-field-custom-value_22"}, + {0x20055A, "data-field-custom-value_23"}, + {0x20055B, "data-field-custom-value_24"}, + {0x20055C, "data-field-custom-value_25"}, + {0x20055D, "data-field-custom-value_26"}, + {0x20055E, "data-field-custom-value_27"}, + {0x20055F, "data-field-custom-value_28"}, +}; + +static int usage_id_cmp(const void *p1, const void *p2) +{ + if (*(int *)p1 < *(int *)p2) + return -1; + + if (*(int *)p1 > *(int *)p2) + return 1; + + return 0; +} + +static ssize_t enable_sensor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", sensor_inst->enable); +} + +static int set_power_report_state(struct hid_sensor_custom *sensor_inst, + bool state) +{ + int power_val = -1; + int report_val = -1; + u32 power_state_usage_id; + u32 report_state_usage_id; + int ret; + + /* + * It is possible that the power/report state ids are not present. + * In this case this function will return success. But if the + * ids are present, then it will return error if set fails. + */ + if (state) { + power_state_usage_id = + HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM; + report_state_usage_id = + HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM; + } else { + power_state_usage_id = + HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM; + report_state_usage_id = + HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM; + } + + if (sensor_inst->power_state) + power_val = hid_sensor_get_usage_index(sensor_inst->hsdev, + sensor_inst->power_state->attribute.report_id, + sensor_inst->power_state->attribute.index, + power_state_usage_id); + if (sensor_inst->report_state) + report_val = hid_sensor_get_usage_index(sensor_inst->hsdev, + sensor_inst->report_state->attribute.report_id, + sensor_inst->report_state->attribute.index, + report_state_usage_id); + + if (power_val >= 0) { + power_val += + sensor_inst->power_state->attribute.logical_minimum; + ret = sensor_hub_set_feature(sensor_inst->hsdev, + sensor_inst->power_state->attribute.report_id, + sensor_inst->power_state->attribute.index, + sizeof(power_val), + &power_val); + if (ret) { + hid_err(sensor_inst->hsdev->hdev, + "Set power state failed\n"); + return ret; + } + } + + if (report_val >= 0) { + report_val += + sensor_inst->report_state->attribute.logical_minimum; + ret = sensor_hub_set_feature(sensor_inst->hsdev, + sensor_inst->report_state->attribute.report_id, + sensor_inst->report_state->attribute.index, + sizeof(report_val), + &report_val); + if (ret) { + hid_err(sensor_inst->hsdev->hdev, + "Set report state failed\n"); + return ret; + } + } + + return 0; +} + +static ssize_t enable_sensor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + int value; + int ret = -EINVAL; + + if (kstrtoint(buf, 0, &value) != 0) + return -EINVAL; + + mutex_lock(&sensor_inst->mutex); + if (value && !sensor_inst->enable) { + ret = sensor_hub_device_open(sensor_inst->hsdev); + if (ret) + goto unlock_state; + + ret = set_power_report_state(sensor_inst, true); + if (ret) { + sensor_hub_device_close(sensor_inst->hsdev); + goto unlock_state; + } + sensor_inst->enable = true; + } else if (!value && sensor_inst->enable) { + ret = set_power_report_state(sensor_inst, false); + sensor_hub_device_close(sensor_inst->hsdev); + sensor_inst->enable = false; + } +unlock_state: + mutex_unlock(&sensor_inst->mutex); + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(enable_sensor); + +static struct attribute *enable_sensor_attrs[] = { + &dev_attr_enable_sensor.attr, + NULL, +}; + +static struct attribute_group enable_sensor_attr_group = { + .attrs = enable_sensor_attrs, +}; + +static ssize_t show_value(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + struct hid_sensor_hub_attribute_info *attribute; + int index, usage, field_index; + char name[HID_CUSTOM_NAME_LENGTH]; + bool feature = false; + bool input = false; + int value = 0; + + if (sscanf(attr->attr.name, "feature-%d-%x-%s", &index, &usage, + name) == 3) { + feature = true; + field_index = index + sensor_inst->input_field_count; + } else if (sscanf(attr->attr.name, "input-%d-%x-%s", &index, &usage, + name) == 3) { + input = true; + field_index = index; + } else + return -EINVAL; + + if (!strncmp(name, "value", strlen("value"))) { + u32 report_id; + int ret; + + attribute = &sensor_inst->fields[field_index].attribute; + report_id = attribute->report_id; + if (feature) { + u8 values[HID_CUSTOM_MAX_FEATURE_BYTES]; + int len = 0; + u64 value = 0; + int i = 0; + + ret = sensor_hub_get_feature(sensor_inst->hsdev, + report_id, + index, + sizeof(values), values); + if (ret < 0) + return ret; + + while (i < ret) { + if (i + attribute->size > ret) { + len += snprintf(&buf[len], + PAGE_SIZE - len, + "%d ", values[i]); + break; + } + switch (attribute->size) { + case 2: + value = (u64) *(u16 *)&values[i]; + i += attribute->size; + break; + case 4: + value = (u64) *(u32 *)&values[i]; + i += attribute->size; + break; + case 8: + value = *(u64 *)&values[i]; + i += attribute->size; + break; + default: + value = (u64) values[i]; + ++i; + break; + } + len += snprintf(&buf[len], PAGE_SIZE - len, + "%lld ", value); + } + len += snprintf(&buf[len], PAGE_SIZE - len, "\n"); + + return len; + } else if (input) + value = sensor_hub_input_attr_get_raw_value( + sensor_inst->hsdev, + sensor_inst->hsdev->usage, + usage, report_id, + SENSOR_HUB_SYNC); + } else if (!strncmp(name, "units", strlen("units"))) + value = sensor_inst->fields[field_index].attribute.units; + else if (!strncmp(name, "unit-expo", strlen("unit-expo"))) + value = sensor_inst->fields[field_index].attribute.unit_expo; + else if (!strncmp(name, "size", strlen("size"))) + value = sensor_inst->fields[field_index].attribute.size; + else if (!strncmp(name, "minimum", strlen("minimum"))) + value = sensor_inst->fields[field_index].attribute. + logical_minimum; + else if (!strncmp(name, "maximum", strlen("maximum"))) + value = sensor_inst->fields[field_index].attribute. + logical_maximum; + else if (!strncmp(name, "name", strlen("name"))) { + struct hid_custom_usage_desc *usage_desc; + + usage_desc = bsearch(&usage, hid_custom_usage_desc_table, + ARRAY_SIZE(hid_custom_usage_desc_table), + sizeof(struct hid_custom_usage_desc), + usage_id_cmp); + if (usage_desc) + return snprintf(buf, PAGE_SIZE, "%s\n", + usage_desc->desc); + else + return sprintf(buf, "not-specified\n"); + } else + return -EINVAL; + + return sprintf(buf, "%d\n", value); +} + +static ssize_t store_value(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + int index, field_index, usage; + char name[HID_CUSTOM_NAME_LENGTH]; + int value; + + if (sscanf(attr->attr.name, "feature-%d-%x-%s", &index, &usage, + name) == 3) { + field_index = index + sensor_inst->input_field_count; + } else + return -EINVAL; + + if (!strncmp(name, "value", strlen("value"))) { + u32 report_id; + int ret; + + if (kstrtoint(buf, 0, &value) != 0) + return -EINVAL; + + report_id = sensor_inst->fields[field_index].attribute. + report_id; + ret = sensor_hub_set_feature(sensor_inst->hsdev, report_id, + index, sizeof(value), &value); + } else + return -EINVAL; + + return count; +} + +static int hid_sensor_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, size_t raw_len, + char *raw_data, void *priv) +{ + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv); + struct hid_sensor_sample header; + + /* If any error occurs in a sample, rest of the fields are ignored */ + if (sensor_inst->input_skip_sample) { + hid_err(sensor_inst->hsdev->hdev, "Skipped remaining data\n"); + return 0; + } + + hid_dbg(sensor_inst->hsdev->hdev, "%s received %d of %d\n", __func__, + (int) (sensor_inst->input_report_recd_size + raw_len), + sensor_inst->input_report_size); + + if (!test_bit(0, &sensor_inst->misc_opened)) + return 0; + + if (!sensor_inst->input_report_recd_size) { + int required_size = sizeof(struct hid_sensor_sample) + + sensor_inst->input_report_size; + header.usage_id = hsdev->usage; + header.raw_len = sensor_inst->input_report_size; + header.timestamp = ktime_get_real_ns(); + if (kfifo_avail(&sensor_inst->data_fifo) >= required_size) { + kfifo_in(&sensor_inst->data_fifo, + (unsigned char *)&header, + sizeof(header)); + } else + sensor_inst->input_skip_sample = true; + } + if (kfifo_avail(&sensor_inst->data_fifo) >= raw_len) + kfifo_in(&sensor_inst->data_fifo, (unsigned char *)raw_data, + raw_len); + + sensor_inst->input_report_recd_size += raw_len; + + return 0; +} + +static int hid_sensor_send_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, void *priv) +{ + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv); + + if (!test_bit(0, &sensor_inst->misc_opened)) + return 0; + + sensor_inst->input_report_recd_size = 0; + sensor_inst->input_skip_sample = false; + + wake_up(&sensor_inst->wait); + + return 0; +} + +static int hid_sensor_custom_add_field(struct hid_sensor_custom *sensor_inst, + int index, int report_type, + struct hid_report *report, + struct hid_field *field) +{ + struct hid_sensor_custom_field *sensor_field; + void *fields; + + fields = krealloc(sensor_inst->fields, + (sensor_inst->sensor_field_count + 1) * + sizeof(struct hid_sensor_custom_field), GFP_KERNEL); + if (!fields) { + kfree(sensor_inst->fields); + return -ENOMEM; + } + sensor_inst->fields = fields; + sensor_field = &sensor_inst->fields[sensor_inst->sensor_field_count]; + sensor_field->attribute.usage_id = sensor_inst->hsdev->usage; + if (field->logical) + sensor_field->attribute.attrib_id = field->logical; + else + sensor_field->attribute.attrib_id = field->usage[0].hid; + + sensor_field->attribute.index = index; + sensor_field->attribute.report_id = report->id; + sensor_field->attribute.units = field->unit; + sensor_field->attribute.unit_expo = field->unit_exponent; + sensor_field->attribute.size = (field->report_size / 8); + sensor_field->attribute.logical_minimum = field->logical_minimum; + sensor_field->attribute.logical_maximum = field->logical_maximum; + + if (report_type == HID_FEATURE_REPORT) + snprintf(sensor_field->group_name, + sizeof(sensor_field->group_name), "feature-%x-%x", + sensor_field->attribute.index, + sensor_field->attribute.attrib_id); + else if (report_type == HID_INPUT_REPORT) { + snprintf(sensor_field->group_name, + sizeof(sensor_field->group_name), + "input-%x-%x", sensor_field->attribute.index, + sensor_field->attribute.attrib_id); + sensor_inst->input_field_count++; + sensor_inst->input_report_size += (field->report_size * + field->report_count) / 8; + } + + memset(&sensor_field->hid_custom_attribute_group, 0, + sizeof(struct attribute_group)); + sensor_inst->sensor_field_count++; + + return 0; +} + +static int hid_sensor_custom_add_fields(struct hid_sensor_custom *sensor_inst, + struct hid_report_enum *report_enum, + int report_type) +{ + int i; + int ret; + struct hid_report *report; + struct hid_field *field; + struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev; + + list_for_each_entry(report, &report_enum->report_list, list) { + for (i = 0; i < report->maxfield; ++i) { + field = report->field[i]; + if (field->maxusage && + ((field->usage[0].collection_index >= + hsdev->start_collection_index) && + (field->usage[0].collection_index < + hsdev->end_collection_index))) { + + ret = hid_sensor_custom_add_field(sensor_inst, + i, + report_type, + report, + field); + if (ret) + return ret; + + } + } + } + + return 0; +} + +static int hid_sensor_custom_add_attributes(struct hid_sensor_custom + *sensor_inst) +{ + struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev; + struct hid_device *hdev = hsdev->hdev; + int ret = -1; + int i, j; + + for (j = 0; j < HID_REPORT_TYPES; ++j) { + if (j == HID_OUTPUT_REPORT) + continue; + + ret = hid_sensor_custom_add_fields(sensor_inst, + &hdev->report_enum[j], j); + if (ret) + return ret; + + } + + /* Create sysfs attributes */ + for (i = 0; i < sensor_inst->sensor_field_count; ++i) { + j = 0; + while (j < HID_CUSTOM_TOTAL_ATTRS && + hid_custom_attrs[j].name) { + struct device_attribute *device_attr; + + device_attr = &sensor_inst->fields[i].sd_attrs[j]; + + snprintf((char *)&sensor_inst->fields[i].attr_name[j], + HID_CUSTOM_NAME_LENGTH, "%s-%s", + sensor_inst->fields[i].group_name, + hid_custom_attrs[j].name); + sysfs_attr_init(&device_attr->attr); + device_attr->attr.name = + (char *)&sensor_inst->fields[i].attr_name[j]; + device_attr->attr.mode = hid_custom_attrs[j].mode; + device_attr->show = show_value; + if (hid_custom_attrs[j].mode & S_IWUSR) + device_attr->store = store_value; + sensor_inst->fields[i].attrs[j] = &device_attr->attr; + ++j; + } + sensor_inst->fields[i].attrs[j] = NULL; + sensor_inst->fields[i].hid_custom_attribute_group.attrs = + sensor_inst->fields[i].attrs; + sensor_inst->fields[i].hid_custom_attribute_group.name = + sensor_inst->fields[i].group_name; + ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj, + &sensor_inst->fields[i]. + hid_custom_attribute_group); + if (ret) + break; + + /* For power or report field store indexes */ + if (sensor_inst->fields[i].attribute.attrib_id == + HID_USAGE_SENSOR_PROY_POWER_STATE) + sensor_inst->power_state = &sensor_inst->fields[i]; + else if (sensor_inst->fields[i].attribute.attrib_id == + HID_USAGE_SENSOR_PROP_REPORT_STATE) + sensor_inst->report_state = &sensor_inst->fields[i]; + } + + return ret; +} + +static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom * + sensor_inst) +{ + int i; + + for (i = 0; i < sensor_inst->sensor_field_count; ++i) + sysfs_remove_group(&sensor_inst->pdev->dev.kobj, + &sensor_inst->fields[i]. + hid_custom_attribute_group); + + kfree(sensor_inst->fields); +} + +static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf, + size_t count, loff_t *f_ps) +{ + struct hid_sensor_custom *sensor_inst; + unsigned int copied; + int ret; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + + if (count < sizeof(struct hid_sensor_sample)) + return -EINVAL; + + do { + if (kfifo_is_empty(&sensor_inst->data_fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(sensor_inst->wait, + !kfifo_is_empty(&sensor_inst->data_fifo)); + if (ret) + return ret; + } + ret = kfifo_to_user(&sensor_inst->data_fifo, buf, count, + &copied); + if (ret) + return ret; + + } while (copied == 0); + + return copied; +} + +static int hid_sensor_custom_release(struct inode *inode, struct file *file) +{ + struct hid_sensor_custom *sensor_inst; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + + clear_bit(0, &sensor_inst->misc_opened); + + return 0; +} + +static int hid_sensor_custom_open(struct inode *inode, struct file *file) +{ + struct hid_sensor_custom *sensor_inst; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + /* We essentially have single reader and writer */ + if (test_and_set_bit(0, &sensor_inst->misc_opened)) + return -EBUSY; + + return nonseekable_open(inode, file); +} + +static unsigned int hid_sensor_custom_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct hid_sensor_custom *sensor_inst; + unsigned int mask = 0; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + + poll_wait(file, &sensor_inst->wait, wait); + + if (!kfifo_is_empty(&sensor_inst->data_fifo)) + mask = POLLIN | POLLRDNORM; + + return mask; +} + +static const struct file_operations hid_sensor_custom_fops = { + .open = hid_sensor_custom_open, + .read = hid_sensor_custom_read, + .release = hid_sensor_custom_release, + .poll = hid_sensor_custom_poll, + .llseek = noop_llseek, +}; + +static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst) +{ + int ret; + + ret = kfifo_alloc(&sensor_inst->data_fifo, HID_CUSTOM_FIFO_SIZE, + GFP_KERNEL); + if (ret) + return ret; + + init_waitqueue_head(&sensor_inst->wait); + + sensor_inst->custom_dev.minor = MISC_DYNAMIC_MINOR; + sensor_inst->custom_dev.name = dev_name(&sensor_inst->pdev->dev); + sensor_inst->custom_dev.fops = &hid_sensor_custom_fops, + ret = misc_register(&sensor_inst->custom_dev); + if (ret) { + kfifo_free(&sensor_inst->data_fifo); + return ret; + } + return 0; +} + +static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom + *sensor_inst) +{ + wake_up(&sensor_inst->wait); + misc_deregister(&sensor_inst->custom_dev); + kfifo_free(&sensor_inst->data_fifo); + +} + +static int hid_sensor_custom_probe(struct platform_device *pdev) +{ + struct hid_sensor_custom *sensor_inst; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + int ret; + + sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst), + GFP_KERNEL); + if (!sensor_inst) + return -ENOMEM; + + sensor_inst->callbacks.capture_sample = hid_sensor_capture_sample; + sensor_inst->callbacks.send_event = hid_sensor_send_event; + sensor_inst->callbacks.pdev = pdev; + sensor_inst->hsdev = hsdev; + sensor_inst->pdev = pdev; + mutex_init(&sensor_inst->mutex); + platform_set_drvdata(pdev, sensor_inst); + ret = sensor_hub_register_callback(hsdev, hsdev->usage, + &sensor_inst->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + return ret; + } + + ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); + if (ret) + goto err_remove_callback; + + ret = hid_sensor_custom_add_attributes(sensor_inst); + if (ret) + goto err_remove_group; + + ret = hid_sensor_custom_dev_if_add(sensor_inst); + if (ret) + goto err_remove_attributes; + + return 0; + +err_remove_attributes: + hid_sensor_custom_remove_attributes(sensor_inst); +err_remove_group: + sysfs_remove_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); +err_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); + + return ret; +} + +static int hid_sensor_custom_remove(struct platform_device *pdev) +{ + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + + hid_sensor_custom_dev_if_remove(sensor_inst); + hid_sensor_custom_remove_attributes(sensor_inst); + sysfs_remove_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); + sensor_hub_remove_callback(hsdev, hsdev->usage); + + return 0; +} + +static struct platform_device_id hid_sensor_custom_ids[] = { + { + .name = "HID-SENSOR-2000e1", + }, + { + .name = "HID-SENSOR-2000e2", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_sensor_custom_ids); + +static struct platform_driver hid_sensor_custom_platform_driver = { + .id_table = hid_sensor_custom_ids, + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = hid_sensor_custom_probe, + .remove = hid_sensor_custom_remove, +}; +module_platform_driver(hid_sensor_custom_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Custom and Generic sensor Driver"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From b2eafd7282fdfd148fc09032540b0ff42bfedfbf Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Fri, 10 Apr 2015 11:22:23 -0700 Subject: HID: sensor: Update document for custom sensor Added custom sensor documentation Signed-off-by: Srinivas Pandruvada Acked-by: Jonathan Cameron Signed-off-by: Jiri Kosina diff --git a/Documentation/hid/hid-sensor.txt b/Documentation/hid/hid-sensor.txt index 948b098..b287752 100644 --- a/Documentation/hid/hid-sensor.txt +++ b/Documentation/hid/hid-sensor.txt @@ -138,3 +138,87 @@ accelerometer wants to poll X axis value, then it can call this function with the usage id of X axis. HID sensors can provide events, so this is not necessary to poll for any field. If there is some new sample, the core driver will call registered callback function to process the sample. + + +---------- + +HID Custom and generic Sensors + +HID Sensor specification defines two special sensor usage types. Since they +don't represent a standard sensor, it is not possible to define using Linux IIO +type interfaces. +The purpose of these sensors is to extend the functionality or provide a +way to obfuscate the data being communicated by a sensor. Without knowing the +mapping between the data and its encapsulated form, it is difficult for +an application/driver to determine what data is being communicated by the sensor. +This allows some differentiating use cases, where vendor can provide applications. +Some common use cases are debug other sensors or to provide some events like +keyboard attached/detached or lid open/close. + +To allow application to utilize these sensors, here they are exported uses sysfs +attribute groups, attributes and misc device interface. + +An example of this representation on sysfs: +/sys/devices/pci0000:00/INT33C2:00/i2c-0/i2c-INT33D1:00/0018:8086:09FA.0001/HID-SENSOR-2000e1.6.auto$ tree -R +. +????????? enable_sensor +????????? feature-0-200316 +??????? ????????? feature-0-200316-maximum +??????? ????????? feature-0-200316-minimum +??????? ????????? feature-0-200316-name +??????? ????????? feature-0-200316-size +??????? ????????? feature-0-200316-unit-expo +??????? ????????? feature-0-200316-units +??????? ????????? feature-0-200316-value +????????? feature-1-200201 +??????? ????????? feature-1-200201-maximum +??????? ????????? feature-1-200201-minimum +??????? ????????? feature-1-200201-name +??????? ????????? feature-1-200201-size +??????? ????????? feature-1-200201-unit-expo +??????? ????????? feature-1-200201-units +??????? ????????? feature-1-200201-value +????????? input-0-200201 +??????? ????????? input-0-200201-maximum +??????? ????????? input-0-200201-minimum +??????? ????????? input-0-200201-name +??????? ????????? input-0-200201-size +??????? ????????? input-0-200201-unit-expo +??????? ????????? input-0-200201-units +??????? ????????? input-0-200201-value +????????? input-1-200202 +??????? ????????? input-1-200202-maximum +??????? ????????? input-1-200202-minimum +??????? ????????? input-1-200202-name +??????? ????????? input-1-200202-size +??????? ????????? input-1-200202-unit-expo +??????? ????????? input-1-200202-units +??????? ????????? input-1-200202-value + +Here there is a custom sensors with four fields, two feature and two inputs. +Each field is represented by a set of attributes. All fields except the "value" +are read only. The value field is a RW field. +Example +/sys/bus/platform/devices/HID-SENSOR-2000e1.6.auto/feature-0-200316$ grep -r . * +feature-0-200316-maximum:6 +feature-0-200316-minimum:0 +feature-0-200316-name:property-reporting-state +feature-0-200316-size:1 +feature-0-200316-unit-expo:0 +feature-0-200316-units:25 +feature-0-200316-value:1 + +How to enable such sensor? +By default sensor can be power gated. To enable sysfs attribute "enable" can be +used. +$ echo 1 > enable_sensor + +Once enabled and powered on, sensor can report value using HID reports. +These reports are pushed using misc device interface in a FIFO order. +/dev$ tree | grep HID-SENSOR-2000e1.6.auto +??????? ????????? 10:53 -> ../HID-SENSOR-2000e1.6.auto +????????? HID-SENSOR-2000e1.6.auto + +Each reports can be of variable length preceded by a header. This header +consist of a 32 bit usage id, 64 bit time stamp and 32 bit length field of raw +data. -- cgit v0.10.2