summaryrefslogtreecommitdiff
path: root/drivers/hid/hid-sensor-custom.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/hid-sensor-custom.c')
-rw-r--r--drivers/hid/hid-sensor-custom.c261
1 files changed, 249 insertions, 12 deletions
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index 4d25577a8573..761760668f6d 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -4,6 +4,8 @@
* Copyright (c) 2015, Intel Corporation.
*/
+#include <linux/ctype.h>
+#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
@@ -21,6 +23,7 @@
#define HID_CUSTOM_TOTAL_ATTRS (HID_CUSTOM_MAX_CORE_ATTRS + 1)
#define HID_CUSTOM_FIFO_SIZE 4096
#define HID_CUSTOM_MAX_FEATURE_BYTES 64
+#define HID_SENSOR_USAGE_LENGTH (4 + 1)
struct hid_sensor_custom_field {
int report_id;
@@ -50,6 +53,7 @@ struct hid_sensor_custom {
struct kfifo data_fifo;
unsigned long misc_opened;
wait_queue_head_t wait;
+ struct platform_device *custom_pdev;
};
/* Header for each sample to user space via dev interface */
@@ -59,7 +63,7 @@ struct hid_sensor_sample {
u32 raw_len;
} __packed;
-static struct attribute hid_custom_attrs[] = {
+static struct attribute hid_custom_attrs[HID_CUSTOM_TOTAL_ATTRS] = {
{.name = "name", .mode = S_IRUGO},
{.name = "units", .mode = S_IRUGO},
{.name = "unit-expo", .mode = S_IRUGO},
@@ -151,7 +155,7 @@ static ssize_t enable_sensor_show(struct device *dev,
{
struct hid_sensor_custom *sensor_inst = dev_get_drvdata(dev);
- return sprintf(buf, "%d\n", sensor_inst->enable);
+ return sysfs_emit(buf, "%d\n", sensor_inst->enable);
}
static int set_power_report_state(struct hid_sensor_custom *sensor_inst,
@@ -368,14 +372,13 @@ static ssize_t show_value(struct device *dev, struct device_attribute *attr,
sizeof(struct hid_custom_usage_desc),
usage_id_cmp);
if (usage_desc)
- return snprintf(buf, PAGE_SIZE, "%s\n",
- usage_desc->desc);
+ return sysfs_emit(buf, "%s\n", usage_desc->desc);
else
- return sprintf(buf, "not-specified\n");
+ return sysfs_emit(buf, "not-specified\n");
} else
return -EINVAL;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t store_value(struct device *dev, struct device_attribute *attr,
@@ -384,7 +387,7 @@ static ssize_t store_value(struct device *dev, struct device_attribute *attr,
struct hid_sensor_custom *sensor_inst = dev_get_drvdata(dev);
int index, field_index, usage;
char name[HID_CUSTOM_NAME_LENGTH];
- int value;
+ int value, ret;
if (sscanf(attr->attr.name, "feature-%x-%x-%s", &index, &usage,
name) == 3) {
@@ -394,7 +397,6 @@ static ssize_t store_value(struct device *dev, struct device_attribute *attr,
if (!strncmp(name, "value", strlen("value"))) {
u32 report_id;
- int ret;
if (kstrtoint(buf, 0, &value) != 0)
return -EINVAL;
@@ -403,6 +405,8 @@ static ssize_t store_value(struct device *dev, struct device_attribute *attr,
report_id;
ret = sensor_hub_set_feature(sensor_inst->hsdev, report_id,
index, sizeof(value), &value);
+ if (ret)
+ return ret;
} else
return -EINVAL;
@@ -728,7 +732,7 @@ static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst)
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,
+ sensor_inst->custom_dev.fops = &hid_sensor_custom_fops;
ret = misc_register(&sensor_inst->custom_dev);
if (ret) {
kfifo_free(&sensor_inst->data_fifo);
@@ -746,11 +750,225 @@ static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
}
+/*
+ * Match a known custom sensor.
+ * tag and luid is mandatory.
+ */
+struct hid_sensor_custom_match {
+ const char *tag;
+ const char *luid;
+ const char *model;
+ const char *manufacturer;
+ bool check_dmi;
+ struct dmi_system_id dmi;
+};
+
+/*
+ * Custom sensor properties used for matching.
+ */
+struct hid_sensor_custom_properties {
+ u16 serial_num[HID_CUSTOM_MAX_FEATURE_BYTES];
+ u16 model[HID_CUSTOM_MAX_FEATURE_BYTES];
+ u16 manufacturer[HID_CUSTOM_MAX_FEATURE_BYTES];
+};
+
+static const struct hid_sensor_custom_match hid_sensor_custom_known_table[] = {
+ /*
+ * Intel Integrated Sensor Hub (ISH)
+ */
+ { /* Intel ISH hinge */
+ .tag = "INT",
+ .luid = "020B000000000000",
+ .manufacturer = "INTEL",
+ },
+ /*
+ * Lenovo Intelligent Sensing Solution (LISS)
+ */
+ { /* ambient light */
+ .tag = "LISS",
+ .luid = "0041010200000082",
+ .model = "STK3X3X Sensor",
+ .manufacturer = "Vendor 258",
+ .check_dmi = true,
+ .dmi.matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ }
+ },
+ { /* human presence */
+ .tag = "LISS",
+ .luid = "0226000171AC0081",
+ .model = "VL53L1_HOD Sensor",
+ .manufacturer = "ST_MICRO",
+ .check_dmi = true,
+ .dmi.matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ }
+ },
+ {}
+};
+
+static bool hid_sensor_custom_prop_match_str(const u16 *prop, const char *match,
+ size_t count)
+{
+ while (count-- && *prop && *match) {
+ if (*prop != (u16) *match)
+ return false;
+ prop++;
+ match++;
+ }
+
+ return (count == -1) || *prop == (u16)*match;
+}
+
+static int hid_sensor_custom_get_prop(struct hid_sensor_hub_device *hsdev,
+ u32 prop_usage_id, size_t prop_size,
+ u16 *prop)
+{
+ struct hid_sensor_hub_attribute_info prop_attr = { 0 };
+ int ret;
+
+ memset(prop, 0, prop_size);
+
+ ret = sensor_hub_input_get_attribute_info(hsdev, HID_FEATURE_REPORT,
+ hsdev->usage, prop_usage_id,
+ &prop_attr);
+ if (ret < 0)
+ return ret;
+
+ ret = sensor_hub_get_feature(hsdev, prop_attr.report_id,
+ prop_attr.index, prop_size, prop);
+ if (ret < 0) {
+ hid_err(hsdev->hdev, "Failed to get sensor property %08x %d\n",
+ prop_usage_id, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool
+hid_sensor_custom_do_match(struct hid_sensor_hub_device *hsdev,
+ const struct hid_sensor_custom_match *match,
+ const struct hid_sensor_custom_properties *prop)
+{
+ struct dmi_system_id dmi[] = { match->dmi, { 0 } };
+
+ if (!hid_sensor_custom_prop_match_str(prop->serial_num, "LUID:", 5) ||
+ !hid_sensor_custom_prop_match_str(prop->serial_num + 5, match->luid,
+ HID_CUSTOM_MAX_FEATURE_BYTES - 5))
+ return false;
+
+ if (match->model &&
+ !hid_sensor_custom_prop_match_str(prop->model, match->model,
+ HID_CUSTOM_MAX_FEATURE_BYTES))
+ return false;
+
+ if (match->manufacturer &&
+ !hid_sensor_custom_prop_match_str(prop->manufacturer, match->manufacturer,
+ HID_CUSTOM_MAX_FEATURE_BYTES))
+ return false;
+
+ if (match->check_dmi && !dmi_check_system(dmi))
+ return false;
+
+ return true;
+}
+
+static int
+hid_sensor_custom_properties_get(struct hid_sensor_hub_device *hsdev,
+ struct hid_sensor_custom_properties *prop)
+{
+ int ret;
+
+ ret = hid_sensor_custom_get_prop(hsdev,
+ HID_USAGE_SENSOR_PROP_SERIAL_NUM,
+ HID_CUSTOM_MAX_FEATURE_BYTES,
+ prop->serial_num);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Ignore errors on the following model and manufacturer properties.
+ * Because these are optional, it is not an error if they are missing.
+ */
+
+ hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MODEL,
+ HID_CUSTOM_MAX_FEATURE_BYTES,
+ prop->model);
+
+ hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MANUFACTURER,
+ HID_CUSTOM_MAX_FEATURE_BYTES,
+ prop->manufacturer);
+
+ return 0;
+}
+
+static int
+hid_sensor_custom_get_known(struct hid_sensor_hub_device *hsdev,
+ const struct hid_sensor_custom_match **known)
+{
+ int ret;
+ const struct hid_sensor_custom_match *match =
+ hid_sensor_custom_known_table;
+ struct hid_sensor_custom_properties *prop;
+
+ prop = kmalloc(sizeof(struct hid_sensor_custom_properties), GFP_KERNEL);
+ if (!prop)
+ return -ENOMEM;
+
+ ret = hid_sensor_custom_properties_get(hsdev, prop);
+ if (ret < 0)
+ goto out;
+
+ while (match->tag) {
+ if (hid_sensor_custom_do_match(hsdev, match, prop)) {
+ *known = match;
+ ret = 0;
+ goto out;
+ }
+ match++;
+ }
+ ret = -ENODATA;
+out:
+ kfree(prop);
+ return ret;
+}
+
+static struct platform_device *
+hid_sensor_register_platform_device(struct platform_device *pdev,
+ struct hid_sensor_hub_device *hsdev,
+ const struct hid_sensor_custom_match *match)
+{
+ char real_usage[HID_SENSOR_USAGE_LENGTH] = { 0 };
+ struct platform_device *custom_pdev;
+ const char *dev_name;
+ char *c;
+
+ memcpy(real_usage, match->luid, 4);
+
+ /* usage id are all lowercase */
+ for (c = real_usage; *c != '\0'; c++)
+ *c = tolower(*c);
+
+ /* HID-SENSOR-TAG-REAL_USAGE_ID */
+ dev_name = kasprintf(GFP_KERNEL, "HID-SENSOR-%s-%s",
+ match->tag, real_usage);
+ if (!dev_name)
+ return ERR_PTR(-ENOMEM);
+
+ custom_pdev = platform_device_register_data(pdev->dev.parent, dev_name,
+ PLATFORM_DEVID_AUTO, hsdev,
+ sizeof(*hsdev));
+ kfree(dev_name);
+ return custom_pdev;
+}
+
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;
+ const struct hid_sensor_custom_match *match;
sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
GFP_KERNEL);
@@ -764,6 +982,22 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
sensor_inst->pdev = pdev;
mutex_init(&sensor_inst->mutex);
platform_set_drvdata(pdev, sensor_inst);
+
+ ret = hid_sensor_custom_get_known(hsdev, &match);
+ if (!ret) {
+ sensor_inst->custom_pdev =
+ hid_sensor_register_platform_device(pdev, hsdev, match);
+
+ ret = PTR_ERR_OR_ZERO(sensor_inst->custom_pdev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "register_platform_device failed\n");
+ return ret;
+ }
+
+ return 0;
+ }
+
ret = sensor_hub_register_callback(hsdev, hsdev->usage,
&sensor_inst->callbacks);
if (ret < 0) {
@@ -797,18 +1031,21 @@ err_remove_callback:
return ret;
}
-static int hid_sensor_custom_remove(struct platform_device *pdev)
+static void 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;
+ if (sensor_inst->custom_pdev) {
+ platform_device_unregister(sensor_inst->custom_pdev);
+ return;
+ }
+
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 const struct platform_device_id hid_sensor_custom_ids[] = {