forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
13 KiB
516 lines
13 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* HID Sensors Driver |
|
* Copyright (c) 2012, Intel Corporation. |
|
*/ |
|
#include <linux/device.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/module.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/irq.h> |
|
#include <linux/kernel.h> |
|
#include <linux/slab.h> |
|
#include <linux/time.h> |
|
|
|
#include <linux/hid-sensor-hub.h> |
|
#include <linux/iio/iio.h> |
|
#include <linux/iio/sysfs.h> |
|
|
|
#define HZ_PER_MHZ 1000000L |
|
|
|
static struct { |
|
u32 usage_id; |
|
int unit; /* 0 for default others from HID sensor spec */ |
|
int scale_val0; /* scale, whole number */ |
|
int scale_val1; /* scale, fraction in nanos */ |
|
} unit_conversion[] = { |
|
{HID_USAGE_SENSOR_ACCEL_3D, 0, 9, 806650000}, |
|
{HID_USAGE_SENSOR_ACCEL_3D, |
|
HID_USAGE_SENSOR_UNITS_METERS_PER_SEC_SQRD, 1, 0}, |
|
{HID_USAGE_SENSOR_ACCEL_3D, |
|
HID_USAGE_SENSOR_UNITS_G, 9, 806650000}, |
|
|
|
{HID_USAGE_SENSOR_GRAVITY_VECTOR, 0, 9, 806650000}, |
|
{HID_USAGE_SENSOR_GRAVITY_VECTOR, |
|
HID_USAGE_SENSOR_UNITS_METERS_PER_SEC_SQRD, 1, 0}, |
|
{HID_USAGE_SENSOR_GRAVITY_VECTOR, |
|
HID_USAGE_SENSOR_UNITS_G, 9, 806650000}, |
|
|
|
{HID_USAGE_SENSOR_GYRO_3D, 0, 0, 17453293}, |
|
{HID_USAGE_SENSOR_GYRO_3D, |
|
HID_USAGE_SENSOR_UNITS_RADIANS_PER_SECOND, 1, 0}, |
|
{HID_USAGE_SENSOR_GYRO_3D, |
|
HID_USAGE_SENSOR_UNITS_DEGREES_PER_SECOND, 0, 17453293}, |
|
|
|
{HID_USAGE_SENSOR_COMPASS_3D, 0, 0, 1000000}, |
|
{HID_USAGE_SENSOR_COMPASS_3D, HID_USAGE_SENSOR_UNITS_GAUSS, 1, 0}, |
|
|
|
{HID_USAGE_SENSOR_INCLINOMETER_3D, 0, 0, 17453293}, |
|
{HID_USAGE_SENSOR_INCLINOMETER_3D, |
|
HID_USAGE_SENSOR_UNITS_DEGREES, 0, 17453293}, |
|
{HID_USAGE_SENSOR_INCLINOMETER_3D, |
|
HID_USAGE_SENSOR_UNITS_RADIANS, 1, 0}, |
|
|
|
{HID_USAGE_SENSOR_ALS, 0, 1, 0}, |
|
{HID_USAGE_SENSOR_ALS, HID_USAGE_SENSOR_UNITS_LUX, 1, 0}, |
|
|
|
{HID_USAGE_SENSOR_PRESSURE, 0, 100, 0}, |
|
{HID_USAGE_SENSOR_PRESSURE, HID_USAGE_SENSOR_UNITS_PASCAL, 0, 1000000}, |
|
|
|
{HID_USAGE_SENSOR_TIME_TIMESTAMP, 0, 1000000000, 0}, |
|
{HID_USAGE_SENSOR_TIME_TIMESTAMP, HID_USAGE_SENSOR_UNITS_MILLISECOND, |
|
1000000, 0}, |
|
|
|
{HID_USAGE_SENSOR_DEVICE_ORIENTATION, 0, 1, 0}, |
|
|
|
{HID_USAGE_SENSOR_RELATIVE_ORIENTATION, 0, 1, 0}, |
|
|
|
{HID_USAGE_SENSOR_GEOMAGNETIC_ORIENTATION, 0, 1, 0}, |
|
|
|
{HID_USAGE_SENSOR_TEMPERATURE, 0, 1000, 0}, |
|
{HID_USAGE_SENSOR_TEMPERATURE, HID_USAGE_SENSOR_UNITS_DEGREES, 1000, 0}, |
|
|
|
{HID_USAGE_SENSOR_HUMIDITY, 0, 1000, 0}, |
|
{HID_USAGE_SENSOR_HINGE, 0, 0, 17453293}, |
|
{HID_USAGE_SENSOR_HINGE, HID_USAGE_SENSOR_UNITS_DEGREES, 0, 17453293}, |
|
}; |
|
|
|
static void simple_div(int dividend, int divisor, int *whole, |
|
int *micro_frac) |
|
{ |
|
int rem; |
|
int exp = 0; |
|
|
|
*micro_frac = 0; |
|
if (divisor == 0) { |
|
*whole = 0; |
|
return; |
|
} |
|
*whole = dividend/divisor; |
|
rem = dividend % divisor; |
|
if (rem) { |
|
while (rem <= divisor) { |
|
rem *= 10; |
|
exp++; |
|
} |
|
*micro_frac = (rem / divisor) * int_pow(10, 6 - exp); |
|
} |
|
} |
|
|
|
static void split_micro_fraction(unsigned int no, int exp, int *val1, int *val2) |
|
{ |
|
int divisor = int_pow(10, exp); |
|
|
|
*val1 = no / divisor; |
|
*val2 = no % divisor * int_pow(10, 6 - exp); |
|
} |
|
|
|
/* |
|
VTF format uses exponent and variable size format. |
|
For example if the size is 2 bytes |
|
0x0067 with VTF16E14 format -> +1.03 |
|
To convert just change to 0x67 to decimal and use two decimal as E14 stands |
|
for 10^-2. |
|
Negative numbers are 2's complement |
|
*/ |
|
static void convert_from_vtf_format(u32 value, int size, int exp, |
|
int *val1, int *val2) |
|
{ |
|
int sign = 1; |
|
|
|
if (value & BIT(size*8 - 1)) { |
|
value = ((1LL << (size * 8)) - value); |
|
sign = -1; |
|
} |
|
exp = hid_sensor_convert_exponent(exp); |
|
if (exp >= 0) { |
|
*val1 = sign * value * int_pow(10, exp); |
|
*val2 = 0; |
|
} else { |
|
split_micro_fraction(value, -exp, val1, val2); |
|
if (*val1) |
|
*val1 = sign * (*val1); |
|
else |
|
*val2 = sign * (*val2); |
|
} |
|
} |
|
|
|
static u32 convert_to_vtf_format(int size, int exp, int val1, int val2) |
|
{ |
|
int divisor; |
|
u32 value; |
|
int sign = 1; |
|
|
|
if (val1 < 0 || val2 < 0) |
|
sign = -1; |
|
exp = hid_sensor_convert_exponent(exp); |
|
if (exp < 0) { |
|
divisor = int_pow(10, 6 + exp); |
|
value = abs(val1) * int_pow(10, -exp); |
|
value += abs(val2) / divisor; |
|
} else { |
|
divisor = int_pow(10, exp); |
|
value = abs(val1) / divisor; |
|
} |
|
if (sign < 0) |
|
value = ((1LL << (size * 8)) - value); |
|
|
|
return value; |
|
} |
|
|
|
s32 hid_sensor_read_poll_value(struct hid_sensor_common *st) |
|
{ |
|
s32 value = 0; |
|
int ret; |
|
|
|
ret = sensor_hub_get_feature(st->hsdev, |
|
st->poll.report_id, |
|
st->poll.index, sizeof(value), &value); |
|
|
|
if (ret < 0 || value < 0) { |
|
return -EINVAL; |
|
} else { |
|
if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) |
|
value = value * 1000; |
|
} |
|
|
|
return value; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_read_poll_value); |
|
|
|
int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st, |
|
int *val1, int *val2) |
|
{ |
|
s32 value; |
|
int ret; |
|
|
|
ret = sensor_hub_get_feature(st->hsdev, |
|
st->poll.report_id, |
|
st->poll.index, sizeof(value), &value); |
|
if (ret < 0 || value < 0) { |
|
*val1 = *val2 = 0; |
|
return -EINVAL; |
|
} else { |
|
if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND) |
|
simple_div(1000, value, val1, val2); |
|
else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) |
|
simple_div(1, value, val1, val2); |
|
else { |
|
*val1 = *val2 = 0; |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
return IIO_VAL_INT_PLUS_MICRO; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_read_samp_freq_value); |
|
|
|
int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st, |
|
int val1, int val2) |
|
{ |
|
s32 value; |
|
int ret; |
|
|
|
if (val1 < 0 || val2 < 0) |
|
return -EINVAL; |
|
|
|
value = val1 * HZ_PER_MHZ + val2; |
|
if (value) { |
|
if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND) |
|
value = NSEC_PER_SEC / value; |
|
else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) |
|
value = USEC_PER_SEC / value; |
|
else |
|
value = 0; |
|
} |
|
ret = sensor_hub_set_feature(st->hsdev, st->poll.report_id, |
|
st->poll.index, sizeof(value), &value); |
|
if (ret < 0 || value < 0) |
|
return -EINVAL; |
|
|
|
ret = sensor_hub_get_feature(st->hsdev, |
|
st->poll.report_id, |
|
st->poll.index, sizeof(value), &value); |
|
if (ret < 0 || value < 0) |
|
return -EINVAL; |
|
|
|
st->poll_interval = value; |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_write_samp_freq_value); |
|
|
|
int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st, |
|
int *val1, int *val2) |
|
{ |
|
s32 value; |
|
int ret; |
|
|
|
ret = sensor_hub_get_feature(st->hsdev, |
|
st->sensitivity.report_id, |
|
st->sensitivity.index, sizeof(value), |
|
&value); |
|
if (ret < 0 || value < 0) { |
|
*val1 = *val2 = 0; |
|
return -EINVAL; |
|
} else { |
|
convert_from_vtf_format(value, st->sensitivity.size, |
|
st->sensitivity.unit_expo, |
|
val1, val2); |
|
} |
|
|
|
return IIO_VAL_INT_PLUS_MICRO; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_read_raw_hyst_value); |
|
|
|
int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st, |
|
int val1, int val2) |
|
{ |
|
s32 value; |
|
int ret; |
|
|
|
if (val1 < 0 || val2 < 0) |
|
return -EINVAL; |
|
|
|
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, sizeof(value), |
|
&value); |
|
if (ret < 0 || value < 0) |
|
return -EINVAL; |
|
|
|
ret = sensor_hub_get_feature(st->hsdev, |
|
st->sensitivity.report_id, |
|
st->sensitivity.index, sizeof(value), |
|
&value); |
|
if (ret < 0 || value < 0) |
|
return -EINVAL; |
|
|
|
st->raw_hystersis = value; |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_write_raw_hyst_value); |
|
|
|
/* |
|
* This fuction applies the unit exponent to the scale. |
|
* For example: |
|
* 9.806650000 ->exp:2-> val0[980]val1[665000000] |
|
* 9.000806000 ->exp:2-> val0[900]val1[80600000] |
|
* 0.174535293 ->exp:2-> val0[17]val1[453529300] |
|
* 1.001745329 ->exp:0-> val0[1]val1[1745329] |
|
* 1.001745329 ->exp:2-> val0[100]val1[174532900] |
|
* 1.001745329 ->exp:4-> val0[10017]val1[453290000] |
|
* 9.806650000 ->exp:-2-> val0[0]val1[98066500] |
|
*/ |
|
static void adjust_exponent_nano(int *val0, int *val1, int scale0, |
|
int scale1, int exp) |
|
{ |
|
int divisor; |
|
int i; |
|
int x; |
|
int res; |
|
int rem; |
|
|
|
if (exp > 0) { |
|
*val0 = scale0 * int_pow(10, exp); |
|
res = 0; |
|
if (exp > 9) { |
|
*val1 = 0; |
|
return; |
|
} |
|
for (i = 0; i < exp; ++i) { |
|
divisor = int_pow(10, 8 - i); |
|
x = scale1 / divisor; |
|
res += int_pow(10, exp - 1 - i) * x; |
|
scale1 = scale1 % divisor; |
|
} |
|
*val0 += res; |
|
*val1 = scale1 * int_pow(10, exp); |
|
} else if (exp < 0) { |
|
exp = abs(exp); |
|
if (exp > 9) { |
|
*val0 = *val1 = 0; |
|
return; |
|
} |
|
divisor = int_pow(10, exp); |
|
*val0 = scale0 / divisor; |
|
rem = scale0 % divisor; |
|
res = 0; |
|
for (i = 0; i < (9 - exp); ++i) { |
|
divisor = int_pow(10, 8 - i); |
|
x = scale1 / divisor; |
|
res += int_pow(10, 8 - exp - i) * x; |
|
scale1 = scale1 % divisor; |
|
} |
|
*val1 = rem * int_pow(10, 9 - exp) + res; |
|
} else { |
|
*val0 = scale0; |
|
*val1 = scale1; |
|
} |
|
} |
|
|
|
int hid_sensor_format_scale(u32 usage_id, |
|
struct hid_sensor_hub_attribute_info *attr_info, |
|
int *val0, int *val1) |
|
{ |
|
int i; |
|
int exp; |
|
|
|
*val0 = 1; |
|
*val1 = 0; |
|
|
|
for (i = 0; i < ARRAY_SIZE(unit_conversion); ++i) { |
|
if (unit_conversion[i].usage_id == usage_id && |
|
unit_conversion[i].unit == attr_info->units) { |
|
exp = hid_sensor_convert_exponent( |
|
attr_info->unit_expo); |
|
adjust_exponent_nano(val0, val1, |
|
unit_conversion[i].scale_val0, |
|
unit_conversion[i].scale_val1, exp); |
|
break; |
|
} |
|
} |
|
|
|
return IIO_VAL_INT_PLUS_NANO; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_format_scale); |
|
|
|
int64_t hid_sensor_convert_timestamp(struct hid_sensor_common *st, |
|
int64_t raw_value) |
|
{ |
|
return st->timestamp_ns_scale * raw_value; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_convert_timestamp); |
|
|
|
static |
|
int hid_sensor_get_reporting_interval(struct hid_sensor_hub_device *hsdev, |
|
u32 usage_id, |
|
struct hid_sensor_common *st) |
|
{ |
|
sensor_hub_input_get_attribute_info(hsdev, |
|
HID_FEATURE_REPORT, usage_id, |
|
HID_USAGE_SENSOR_PROP_REPORT_INTERVAL, |
|
&st->poll); |
|
/* Default unit of measure is milliseconds */ |
|
if (st->poll.units == 0) |
|
st->poll.units = HID_USAGE_SENSOR_UNITS_MILLISECOND; |
|
|
|
st->poll_interval = -1; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
static void hid_sensor_get_report_latency_info(struct hid_sensor_hub_device *hsdev, |
|
u32 usage_id, |
|
struct hid_sensor_common *st) |
|
{ |
|
sensor_hub_input_get_attribute_info(hsdev, HID_FEATURE_REPORT, |
|
usage_id, |
|
HID_USAGE_SENSOR_PROP_REPORT_LATENCY, |
|
&st->report_latency); |
|
|
|
hid_dbg(hsdev->hdev, "Report latency attributes: %x:%x\n", |
|
st->report_latency.index, st->report_latency.report_id); |
|
} |
|
|
|
int hid_sensor_get_report_latency(struct hid_sensor_common *st) |
|
{ |
|
int ret; |
|
int value; |
|
|
|
ret = sensor_hub_get_feature(st->hsdev, st->report_latency.report_id, |
|
st->report_latency.index, sizeof(value), |
|
&value); |
|
if (ret < 0) |
|
return ret; |
|
|
|
return value; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_get_report_latency); |
|
|
|
int hid_sensor_set_report_latency(struct hid_sensor_common *st, int latency_ms) |
|
{ |
|
return sensor_hub_set_feature(st->hsdev, st->report_latency.report_id, |
|
st->report_latency.index, |
|
sizeof(latency_ms), &latency_ms); |
|
} |
|
EXPORT_SYMBOL(hid_sensor_set_report_latency); |
|
|
|
bool hid_sensor_batch_mode_supported(struct hid_sensor_common *st) |
|
{ |
|
return st->report_latency.index > 0 && st->report_latency.report_id > 0; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_batch_mode_supported); |
|
|
|
int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, |
|
u32 usage_id, |
|
struct hid_sensor_common *st) |
|
{ |
|
|
|
struct hid_sensor_hub_attribute_info timestamp; |
|
s32 value; |
|
int ret; |
|
|
|
hid_sensor_get_reporting_interval(hsdev, usage_id, st); |
|
|
|
sensor_hub_input_get_attribute_info(hsdev, |
|
HID_FEATURE_REPORT, usage_id, |
|
HID_USAGE_SENSOR_PROP_REPORT_STATE, |
|
&st->report_state); |
|
|
|
sensor_hub_input_get_attribute_info(hsdev, |
|
HID_FEATURE_REPORT, usage_id, |
|
HID_USAGE_SENSOR_PROY_POWER_STATE, |
|
&st->power_state); |
|
|
|
st->power_state.logical_minimum = 1; |
|
st->report_state.logical_minimum = 1; |
|
|
|
sensor_hub_input_get_attribute_info(hsdev, |
|
HID_FEATURE_REPORT, usage_id, |
|
HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS, |
|
&st->sensitivity); |
|
|
|
st->raw_hystersis = -1; |
|
|
|
sensor_hub_input_get_attribute_info(hsdev, |
|
HID_INPUT_REPORT, usage_id, |
|
HID_USAGE_SENSOR_TIME_TIMESTAMP, |
|
×tamp); |
|
if (timestamp.index >= 0 && timestamp.report_id) { |
|
int val0, val1; |
|
|
|
hid_sensor_format_scale(HID_USAGE_SENSOR_TIME_TIMESTAMP, |
|
×tamp, &val0, &val1); |
|
st->timestamp_ns_scale = val0; |
|
} else |
|
st->timestamp_ns_scale = 1000000000; |
|
|
|
hid_sensor_get_report_latency_info(hsdev, usage_id, st); |
|
|
|
hid_dbg(hsdev->hdev, "common attributes: %x:%x, %x:%x, %x:%x %x:%x %x:%x\n", |
|
st->poll.index, st->poll.report_id, |
|
st->report_state.index, st->report_state.report_id, |
|
st->power_state.index, st->power_state.report_id, |
|
st->sensitivity.index, st->sensitivity.report_id, |
|
timestamp.index, timestamp.report_id); |
|
|
|
ret = sensor_hub_get_feature(hsdev, |
|
st->power_state.report_id, |
|
st->power_state.index, sizeof(value), &value); |
|
if (ret < 0) |
|
return ret; |
|
if (value < 0) |
|
return -EINVAL; |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(hid_sensor_parse_common_attributes); |
|
|
|
MODULE_AUTHOR("Srinivas Pandruvada <[email protected]>"); |
|
MODULE_DESCRIPTION("HID Sensor common attribute processing"); |
|
MODULE_LICENSE("GPL");
|
|
|