From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/iio/common/Kconfig | 12 + drivers/iio/common/Makefile | 17 + drivers/iio/common/cros_ec_sensors/Kconfig | 41 + drivers/iio/common/cros_ec_sensors/Makefile | 10 + .../iio/common/cros_ec_sensors/cros_ec_activity.c | 301 +++++++ .../iio/common/cros_ec_sensors/cros_ec_lid_angle.c | 137 ++++ .../iio/common/cros_ec_sensors/cros_ec_sensors.c | 322 ++++++++ .../common/cros_ec_sensors/cros_ec_sensors_core.c | 886 +++++++++++++++++++++ .../common/cros_ec_sensors/cros_ec_sensors_trace.c | 32 + .../common/cros_ec_sensors/cros_ec_sensors_trace.h | 56 ++ drivers/iio/common/hid-sensors/Kconfig | 30 + drivers/iio/common/hid-sensors/Makefile | 8 + .../iio/common/hid-sensors/hid-sensor-attributes.c | 593 ++++++++++++++ .../iio/common/hid-sensors/hid-sensor-trigger.c | 341 ++++++++ .../iio/common/hid-sensors/hid-sensor-trigger.h | 23 + drivers/iio/common/inv_sensors/Kconfig | 7 + drivers/iio/common/inv_sensors/Makefile | 6 + .../iio/common/inv_sensors/inv_sensors_timestamp.c | 212 +++++ drivers/iio/common/ms_sensors/Kconfig | 7 + drivers/iio/common/ms_sensors/Makefile | 6 + drivers/iio/common/ms_sensors/ms_sensors_i2c.c | 697 ++++++++++++++++ drivers/iio/common/ms_sensors/ms_sensors_i2c.h | 74 ++ drivers/iio/common/scmi_sensors/Kconfig | 18 + drivers/iio/common/scmi_sensors/Makefile | 5 + drivers/iio/common/scmi_sensors/scmi_iio.c | 710 +++++++++++++++++ drivers/iio/common/ssp_sensors/Kconfig | 27 + drivers/iio/common/ssp_sensors/Makefile | 9 + drivers/iio/common/ssp_sensors/ssp.h | 246 ++++++ drivers/iio/common/ssp_sensors/ssp_dev.c | 668 ++++++++++++++++ drivers/iio/common/ssp_sensors/ssp_iio.c | 97 +++ drivers/iio/common/ssp_sensors/ssp_iio_sensor.h | 60 ++ drivers/iio/common/ssp_sensors/ssp_spi.c | 601 ++++++++++++++ drivers/iio/common/st_sensors/Kconfig | 15 + drivers/iio/common/st_sensors/Makefile | 11 + drivers/iio/common/st_sensors/st_sensors_buffer.c | 79 ++ drivers/iio/common/st_sensors/st_sensors_core.c | 666 ++++++++++++++++ drivers/iio/common/st_sensors/st_sensors_core.h | 10 + drivers/iio/common/st_sensors/st_sensors_i2c.c | 68 ++ drivers/iio/common/st_sensors/st_sensors_spi.c | 120 +++ drivers/iio/common/st_sensors/st_sensors_trigger.c | 238 ++++++ 40 files changed, 7466 insertions(+) create mode 100644 drivers/iio/common/Kconfig create mode 100644 drivers/iio/common/Makefile create mode 100644 drivers/iio/common/cros_ec_sensors/Kconfig create mode 100644 drivers/iio/common/cros_ec_sensors/Makefile create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_activity.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h create mode 100644 drivers/iio/common/hid-sensors/Kconfig create mode 100644 drivers/iio/common/hid-sensors/Makefile create mode 100644 drivers/iio/common/hid-sensors/hid-sensor-attributes.c create mode 100644 drivers/iio/common/hid-sensors/hid-sensor-trigger.c create mode 100644 drivers/iio/common/hid-sensors/hid-sensor-trigger.h create mode 100644 drivers/iio/common/inv_sensors/Kconfig create mode 100644 drivers/iio/common/inv_sensors/Makefile create mode 100644 drivers/iio/common/inv_sensors/inv_sensors_timestamp.c create mode 100644 drivers/iio/common/ms_sensors/Kconfig create mode 100644 drivers/iio/common/ms_sensors/Makefile create mode 100644 drivers/iio/common/ms_sensors/ms_sensors_i2c.c create mode 100644 drivers/iio/common/ms_sensors/ms_sensors_i2c.h create mode 100644 drivers/iio/common/scmi_sensors/Kconfig create mode 100644 drivers/iio/common/scmi_sensors/Makefile create mode 100644 drivers/iio/common/scmi_sensors/scmi_iio.c create mode 100644 drivers/iio/common/ssp_sensors/Kconfig create mode 100644 drivers/iio/common/ssp_sensors/Makefile create mode 100644 drivers/iio/common/ssp_sensors/ssp.h create mode 100644 drivers/iio/common/ssp_sensors/ssp_dev.c create mode 100644 drivers/iio/common/ssp_sensors/ssp_iio.c create mode 100644 drivers/iio/common/ssp_sensors/ssp_iio_sensor.h create mode 100644 drivers/iio/common/ssp_sensors/ssp_spi.c create mode 100644 drivers/iio/common/st_sensors/Kconfig create mode 100644 drivers/iio/common/st_sensors/Makefile create mode 100644 drivers/iio/common/st_sensors/st_sensors_buffer.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_core.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_core.h create mode 100644 drivers/iio/common/st_sensors/st_sensors_i2c.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_spi.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_trigger.c (limited to 'drivers/iio/common') diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig new file mode 100644 index 000000000..1ccb5ccf3 --- /dev/null +++ b/drivers/iio/common/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# IIO common modules +# + +source "drivers/iio/common/cros_ec_sensors/Kconfig" +source "drivers/iio/common/hid-sensors/Kconfig" +source "drivers/iio/common/inv_sensors/Kconfig" +source "drivers/iio/common/ms_sensors/Kconfig" +source "drivers/iio/common/scmi_sensors/Kconfig" +source "drivers/iio/common/ssp_sensors/Kconfig" +source "drivers/iio/common/st_sensors/Kconfig" diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile new file mode 100644 index 000000000..d3e952239 --- /dev/null +++ b/drivers/iio/common/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the IIO common modules. +# Common modules contains modules, which can be shared among multiple +# IIO modules. For example if the trigger processing is common for +# multiple IIO modules then this can be moved to a common module +# instead of duplicating in each module. +# + +# When adding new entries keep the list in alphabetical order +obj-y += cros_ec_sensors/ +obj-y += hid-sensors/ +obj-y += inv_sensors/ +obj-y += ms_sensors/ +obj-y += scmi_sensors/ +obj-y += ssp_sensors/ +obj-y += st_sensors/ diff --git a/drivers/iio/common/cros_ec_sensors/Kconfig b/drivers/iio/common/cros_ec_sensors/Kconfig new file mode 100644 index 000000000..394e319c9 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/Kconfig @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Chrome OS Embedded Controller managed sensors library +# +config IIO_CROS_EC_SENSORS_CORE + tristate "ChromeOS EC Sensors Core" + depends on SYSFS && CROS_EC_SENSORHUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Base module for the ChromeOS EC Sensors module. + Contains core functions used by other IIO CrosEC sensor + drivers. + Define common attributes and sysfs interrupt handler. + +config IIO_CROS_EC_SENSORS + tristate "ChromeOS EC Contiguous Sensors" + depends on IIO_CROS_EC_SENSORS_CORE + help + Module to handle 3d contiguous sensors like + Accelerometers, Gyroscope and Magnetometer that are + presented by the ChromeOS EC Sensor hub. + Creates an IIO device for each functions. + +config IIO_CROS_EC_SENSORS_LID_ANGLE + tristate "ChromeOS EC Sensor for lid angle" + depends on IIO_CROS_EC_SENSORS_CORE + help + Module to report the angle between lid and base for some + convertible devices. + This module is loaded when the EC can calculate the angle between the base + and the lid. + +config IIO_CROS_EC_ACTIVITY + tristate "ChromeOS EC Activity Sensors" + depends on IIO_CROS_EC_SENSORS_CORE + help + Module to handle activity events presented by the ChromeOS EC sensor hub. + Activities can be a proximity detector (on body/off body detection) + or a significant motion detector. + Creates an IIO device to manage all activities. diff --git a/drivers/iio/common/cros_ec_sensors/Makefile b/drivers/iio/common/cros_ec_sensors/Makefile new file mode 100644 index 000000000..a7dfb5794 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for sensors seen through the ChromeOS EC sensor hub. +# + +cros-ec-sensors-core-objs += cros_ec_sensors_core.o cros_ec_sensors_trace.o +obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros-ec-sensors-core.o +obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o +obj-$(CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE) += cros_ec_lid_angle.o +obj-$(CONFIG_IIO_CROS_EC_ACTIVITY) += cros_ec_activity.o diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_activity.c b/drivers/iio/common/cros_ec_sensors/cros_ec_activity.c new file mode 100644 index 000000000..6762685e6 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_activity.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_activity - Driver for activities/gesture recognition. + * + * Copyright 2025 Google, Inc + * + * This driver uses the cros-ec interface to communicate with the ChromeOS + * EC about activity data. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define DRV_NAME "cros-ec-activity" + +/* state data for ec_sensors iio driver. */ +struct cros_ec_sensors_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec *channels; + + int body_detection_channel_index; + int sig_motion_channel_index; +}; + +static const struct iio_event_spec cros_ec_activity_single_shot[] = { + { + .type = IIO_EV_TYPE_CHANGE, + /* significant motion trigger when we get out of still. */ + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec cros_ec_body_detect_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static int cros_ec_activity_sensors_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_PROXIMITY || mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + guard(mutex)(&st->core.cmd_lock); + st->core.param.cmd = MOTIONSENSE_CMD_GET_ACTIVITY; + st->core.param.get_activity.activity = + MOTIONSENSE_ACTIVITY_BODY_DETECTION; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + return ret; + + /* + * EC actually report if a body is near (1) or far (0). + * Units for proximity sensor after scale is in meter, + * so invert the result to return 0m when near and 1m when far. + */ + *val = !st->core.resp->get_activity.state; + return IIO_VAL_INT; +} + +static int cros_ec_activity_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_ACTIVITY && chan->type != IIO_PROXIMITY) + return -EINVAL; + + guard(mutex)(&st->core.cmd_lock); + st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + return ret; + + switch (chan->type) { + case IIO_PROXIMITY: + return !!(st->core.resp->list_activities.enabled & + (1 << MOTIONSENSE_ACTIVITY_BODY_DETECTION)); + case IIO_ACTIVITY: + if (chan->channel2 == IIO_MOD_STILL) { + return !!(st->core.resp->list_activities.enabled & + (1 << MOTIONSENSE_ACTIVITY_SIG_MOTION)); + } + + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", + chan->channel2); + return -EINVAL; + default: + dev_warn(&indio_dev->dev, "Unknown channel type: %d\n", + chan->type); + return -EINVAL; + } +} + +static int cros_ec_activity_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->core.cmd_lock); + st->core.param.cmd = MOTIONSENSE_CMD_SET_ACTIVITY; + switch (chan->type) { + case IIO_PROXIMITY: + st->core.param.set_activity.activity = + MOTIONSENSE_ACTIVITY_BODY_DETECTION; + break; + case IIO_ACTIVITY: + if (chan->channel2 == IIO_MOD_STILL) { + st->core.param.set_activity.activity = + MOTIONSENSE_ACTIVITY_SIG_MOTION; + break; + } + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", + chan->channel2); + return -EINVAL; + default: + dev_warn(&indio_dev->dev, "Unknown channel type: %d\n", + chan->type); + return -EINVAL; + } + st->core.param.set_activity.enable = state; + return cros_ec_motion_send_host_cmd(&st->core, 0); +} + +static int cros_ec_activity_push_data(struct iio_dev *indio_dev, + s16 *data, s64 timestamp) +{ + struct ec_response_activity_data *activity_data = + (struct ec_response_activity_data *)data; + enum motionsensor_activity activity = activity_data->activity; + u8 state = activity_data->state; + const struct cros_ec_sensors_state *st = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + enum iio_event_direction dir; + int index; + + switch (activity) { + case MOTIONSENSE_ACTIVITY_BODY_DETECTION: + index = st->body_detection_channel_index; + dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + break; + case MOTIONSENSE_ACTIVITY_SIG_MOTION: + index = st->sig_motion_channel_index; + dir = IIO_EV_DIR_FALLING; + break; + default: + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", activity); + return 0; + } + chan = &st->channels[index]; + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, index, chan->event_spec[0].type, dir), + timestamp); + return 0; +} + +static irqreturn_t cros_ec_activity_capture(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + /* + * This callback would be called when a software trigger is + * used. But when this virtual sensor is present, it is guaranteed + * the sensor hub is advanced enough to not need a software trigger. + */ + dev_warn(&indio_dev->dev, "%s: Not Expected\n", __func__); + return IRQ_NONE; +} + +static const struct iio_info ec_sensors_info = { + .read_raw = &cros_ec_activity_sensors_read_raw, + .read_event_config = cros_ec_activity_read_event_config, + .write_event_config = cros_ec_activity_write_event_config, +}; + +static int cros_ec_sensors_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_device *ec_device = dev_get_drvdata(dev->parent); + struct iio_dev *indio_dev; + struct cros_ec_sensors_state *st; + struct iio_chan_spec *channel; + unsigned long activities; + int i, index, ret, nb_activities; + + if (!ec_device) { + dev_warn(dev, "No CROS EC device found.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_activity_capture); + if (ret) + return ret; + + indio_dev->info = &ec_sensors_info; + st = iio_priv(indio_dev); + st->core.type = st->core.resp->info.type; + st->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + return ret; + + activities = st->core.resp->list_activities.enabled | + st->core.resp->list_activities.disabled; + if (!activities) + return -ENODEV; + + /* Allocate a channel per activity and one for timestamp */ + nb_activities = hweight_long(activities) + 1; + st->channels = devm_kcalloc(dev, nb_activities, + sizeof(*st->channels), GFP_KERNEL); + if (!st->channels) + return -ENOMEM; + + channel = &st->channels[0]; + index = 0; + for_each_set_bit(i, &activities, BITS_PER_LONG) { + /* List all available triggers */ + if (i == MOTIONSENSE_ACTIVITY_BODY_DETECTION) { + channel->type = IIO_PROXIMITY; + channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + channel->event_spec = cros_ec_body_detect_events; + channel->num_event_specs = + ARRAY_SIZE(cros_ec_body_detect_events); + st->body_detection_channel_index = index; + } else { + channel->type = IIO_ACTIVITY; + channel->modified = 1; + channel->event_spec = cros_ec_activity_single_shot; + channel->num_event_specs = + ARRAY_SIZE(cros_ec_activity_single_shot); + if (i == MOTIONSENSE_ACTIVITY_SIG_MOTION) { + channel->channel2 = IIO_MOD_STILL; + st->sig_motion_channel_index = index; + } else { + dev_warn(dev, "Unknown activity: %d\n", i); + continue; + } + } + channel->ext_info = cros_ec_sensors_limited_info; + channel->scan_index = index++; + channel++; + } + + *channel = IIO_CHAN_SOFT_TIMESTAMP(index); + + indio_dev->channels = st->channels; + indio_dev->num_channels = index + 1; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_activity_push_data); +} + +static struct platform_driver cros_ec_sensors_platform_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_sensors_probe, +}; +module_platform_driver(cros_ec_sensors_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC activity sensors driver"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c b/drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c new file mode 100644 index 000000000..8f5bf40a0 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * cros_ec_lid_angle - Driver for CrOS EC lid angle sensor. + * + * Copyright 2018 Google, Inc + * + * This driver uses the cros-ec interface to communicate with the Chrome OS + * EC about counter sensors. Counters are presented through + * iio sysfs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "cros-ec-lid-angle" + +/* + * One channel for the lid angle, the other for timestamp. + */ +static const struct iio_chan_spec cros_ec_lid_angle_channels[] = { + { + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type.realbits = CROS_EC_SENSOR_BITS, + .scan_type.storagebits = CROS_EC_SENSOR_BITS, + .scan_type.sign = 'u', + .type = IIO_ANGL + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +/* State data for ec_sensors iio driver. */ +struct cros_ec_lid_angle_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; +}; + +static int cros_ec_sensors_read_lid_angle(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + + st->param.cmd = MOTIONSENSE_CMD_LID_ANGLE; + ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->lid_angle)); + if (ret) { + dev_warn(&indio_dev->dev, "Unable to read lid angle\n"); + return ret; + } + + *data = st->resp->lid_angle.value; + return 0; +} + +static int cros_ec_lid_angle_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_lid_angle_state *st = iio_priv(indio_dev); + s16 data; + int ret; + + mutex_lock(&st->core.cmd_lock); + ret = cros_ec_sensors_read_lid_angle(indio_dev, 1, &data); + if (ret == 0) { + *val = data; + ret = IIO_VAL_INT; + } + mutex_unlock(&st->core.cmd_lock); + return ret; +} + +static const struct iio_info cros_ec_lid_angle_info = { + .read_raw = &cros_ec_lid_angle_read, +}; + +static int cros_ec_lid_angle_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct cros_ec_lid_angle_state *state; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, false, NULL); + if (ret) + return ret; + + indio_dev->info = &cros_ec_lid_angle_info; + state = iio_priv(indio_dev); + indio_dev->channels = cros_ec_lid_angle_channels; + indio_dev->num_channels = ARRAY_SIZE(cros_ec_lid_angle_channels); + + state->core.read_ec_sensors_data = cros_ec_sensors_read_lid_angle; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + cros_ec_sensors_capture, NULL); + if (ret) + return ret; + + return cros_ec_sensors_core_register(dev, indio_dev, NULL); +} + +static const struct platform_device_id cros_ec_lid_angle_ids[] = { + { + .name = DRV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_lid_angle_ids); + +static struct platform_driver cros_ec_lid_angle_platform_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_lid_angle_probe, + .id_table = cros_ec_lid_angle_ids, +}; +module_platform_driver(cros_ec_lid_angle_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC driver for reporting convertible lid angle."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c new file mode 100644 index 000000000..b971f8b64 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_sensors - Driver for Chrome OS Embedded Controller sensors. + * + * Copyright (C) 2016 Google, Inc + * + * This driver uses the cros-ec interface to communicate with the Chrome OS + * EC about sensors data. Data access is presented through iio sysfs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CROS_EC_SENSORS_MAX_CHANNELS 4 + +/* State data for ec_sensors iio driver. */ +struct cros_ec_sensors_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_SENSORS_MAX_CHANNELS]; +}; + +static int cros_ec_sensors_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + s16 data = 0; + s64 val64; + int i; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = st->core.read_ec_sensors_data(indio_dev, 1 << idx, &data); + if (ret < 0) + break; + ret = IIO_VAL_INT; + *val = data; + break; + case IIO_CHAN_INFO_CALIBBIAS: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = 0; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret < 0) + break; + + /* Save values */ + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.calib[i].offset = + st->core.resp->sensor_offset.offset[i]; + ret = IIO_VAL_INT; + *val = st->core.calib[idx].offset; + break; + case IIO_CHAN_INFO_CALIBSCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_SCALE; + st->core.param.sensor_offset.flags = 0; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == -EPROTO || ret == -EOPNOTSUPP) { + /* Reading calibscale is not supported on older EC. */ + *val = 1; + *val2 = 0; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + } else if (ret) { + break; + } + + /* Save values */ + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.calib[i].scale = + st->core.resp->sensor_scale.scale[i]; + + *val = st->core.calib[idx].scale >> 15; + *val2 = ((st->core.calib[idx].scale & 0x7FFF) * 1000000LL) / + MOTION_SENSE_DEFAULT_SCALE; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret < 0) + break; + + val64 = st->core.resp->sensor_range.ret; + switch (st->core.type) { + case MOTIONSENSE_TYPE_ACCEL: + /* + * EC returns data in g, IIO expects m/s^2. + * Do not use IIO_G_TO_M_S_2 to avoid precision loss. + */ + *val = div_s64(val64 * 980665, 10); + *val2 = 10000 << (CROS_EC_SENSOR_BITS - 1); + ret = IIO_VAL_FRACTIONAL; + break; + case MOTIONSENSE_TYPE_GYRO: + /* + * EC returns data in dps, iio expects rad/s. + * Do not use IIO_DEGREE_TO_RAD to avoid precision + * loss. Round to the nearest integer. + */ + *val = 0; + *val2 = div_s64(val64 * 3141592653ULL, + 180 << (CROS_EC_SENSOR_BITS - 1)); + ret = IIO_VAL_INT_PLUS_NANO; + break; + case MOTIONSENSE_TYPE_MAG: + /* + * EC returns data in 16LSB / uT, + * iio expects Gauss + */ + *val = val64; + *val2 = 100 << (CROS_EC_SENSOR_BITS - 1); + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = -EINVAL; + } + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_sensors_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + int i; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + st->core.calib[idx].offset = val; + + /* Send to EC for each axis, even if not complete */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = + MOTION_SENSE_SET_OFFSET; + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.param.sensor_offset.offset[i] = + st->core.calib[i].offset; + st->core.param.sensor_offset.temp = + EC_MOTION_SENSE_INVALID_CALIB_TEMP; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + break; + case IIO_CHAN_INFO_CALIBSCALE: + st->core.calib[idx].scale = val; + /* Send to EC for each axis, even if not complete */ + + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_SCALE; + st->core.param.sensor_offset.flags = + MOTION_SENSE_SET_OFFSET; + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.param.sensor_scale.scale[i] = + st->core.calib[i].scale; + st->core.param.sensor_scale.temp = + EC_MOTION_SENSE_INVALID_CALIB_TEMP; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + break; + case IIO_CHAN_INFO_SCALE: + if (st->core.type == MOTIONSENSE_TYPE_MAG) { + ret = -EINVAL; + break; + } + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = val; + + /* Always roundup, so caller gets at least what it asks for. */ + st->core.param.sensor_range.roundup = 1; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == 0) { + st->core.range_updated = true; + st->core.curr_range = val; + } + break; + default: + ret = cros_ec_sensors_core_write( + &st->core, chan, val, val2, mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info ec_sensors_info = { + .read_raw = &cros_ec_sensors_read, + .write_raw = &cros_ec_sensors_write, + .read_avail = &cros_ec_sensors_core_read_avail, +}; + +static int cros_ec_sensors_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct cros_ec_sensors_state *state; + struct iio_chan_spec *channel; + int ret, i; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_sensors_capture); + if (ret) + return ret; + + indio_dev->info = &ec_sensors_info; + state = iio_priv(indio_dev); + for (channel = state->channels, i = CROS_EC_SENSOR_X; + i < CROS_EC_SENSOR_MAX_AXIS; i++, channel++) { + /* Common part */ + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_index = i; + channel->ext_info = cros_ec_sensors_ext_info; + channel->modified = 1; + channel->channel2 = IIO_MOD_X + i; + channel->scan_type.sign = 's'; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_ACCEL: + channel->type = IIO_ACCEL; + break; + case MOTIONSENSE_TYPE_GYRO: + channel->type = IIO_ANGL_VEL; + break; + case MOTIONSENSE_TYPE_MAG: + channel->type = IIO_MAGN; + break; + default: + dev_err(&pdev->dev, "Unknown motion sensor\n"); + return -EINVAL; + } + } + + *channel = IIO_CHAN_SOFT_TIMESTAMP(CROS_EC_SENSOR_MAX_AXIS); + + indio_dev->channels = state->channels; + indio_dev->num_channels = CROS_EC_SENSORS_MAX_CHANNELS; + + /* There is only enough room for accel and gyro in the io space */ + if ((state->core.ec->cmd_readmem != NULL) && + (state->core.type != MOTIONSENSE_TYPE_MAG)) + state->core.read_ec_sensors_data = cros_ec_sensors_read_lpc; + else + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_sensors_push_data); +} + +static const struct platform_device_id cros_ec_sensors_ids[] = { + { + .name = "cros-ec-accel", + }, + { + .name = "cros-ec-gyro", + }, + { + .name = "cros-ec-mag", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_sensors_ids); + +static struct platform_driver cros_ec_sensors_platform_driver = { + .driver = { + .name = "cros-ec-sensors", + .pm = &cros_ec_sensors_pm_ops, + }, + .probe = cros_ec_sensors_probe, + .id_table = cros_ec_sensors_ids, +}; +module_platform_driver(cros_ec_sensors_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC 3-axis sensors driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c new file mode 100644 index 000000000..5133755c2 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_sensors_core - Common function for Chrome OS EC sensor driver. + * + * Copyright (C) 2016 Google, Inc + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cros_ec_sensors_trace.h" + +/* + * Hard coded to the first device to support sensor fifo. The EC has a 2048 + * byte fifo and will trigger an interrupt when fifo is 2/3 full. + */ +#define CROS_EC_FIFO_SIZE (2048 * 2 / 3) + +static int cros_ec_get_host_cmd_version_mask(struct cros_ec_device *ec_dev, + u16 cmd_offset, u16 cmd, u32 *mask) +{ + DEFINE_RAW_FLEX(struct cros_ec_command, buf, data, + MAX(sizeof(struct ec_response_get_cmd_versions), + sizeof(struct ec_params_get_cmd_versions))); + int ret; + + buf->command = EC_CMD_GET_CMD_VERSIONS + cmd_offset; + buf->insize = sizeof(struct ec_response_get_cmd_versions); + buf->outsize = sizeof(struct ec_params_get_cmd_versions); + ((struct ec_params_get_cmd_versions *)buf->data)->cmd = cmd; + + ret = cros_ec_cmd_xfer_status(ec_dev, buf); + if (ret >= 0) + *mask = ((struct ec_response_get_cmd_versions *)buf->data)->version_mask; + return ret; +} + +static void get_default_min_max_freq(enum motionsensor_type type, + u32 *min_freq, + u32 *max_freq, + u32 *max_fifo_events) +{ + /* + * We don't know fifo size, set to size previously used by older + * hardware. + */ + *max_fifo_events = CROS_EC_FIFO_SIZE; + + switch (type) { + case MOTIONSENSE_TYPE_ACCEL: + *min_freq = 12500; + *max_freq = 100000; + break; + case MOTIONSENSE_TYPE_GYRO: + *min_freq = 25000; + *max_freq = 100000; + break; + case MOTIONSENSE_TYPE_MAG: + *min_freq = 5000; + *max_freq = 25000; + break; + case MOTIONSENSE_TYPE_PROX: + case MOTIONSENSE_TYPE_LIGHT: + *min_freq = 100; + *max_freq = 50000; + break; + case MOTIONSENSE_TYPE_BARO: + *min_freq = 250; + *max_freq = 20000; + break; + case MOTIONSENSE_TYPE_ACTIVITY: + default: + *min_freq = 0; + *max_freq = 0; + break; + } +} + +static ssize_t cros_ec_sensor_set_report_latency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int integer, fract, ret; + int latency; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + /* EC rate is in ms. */ + latency = integer * 1000 + fract / 1000; + + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_EC_RATE; + st->param.ec_rate.data = min(U16_MAX, latency); + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret < 0) { + mutex_unlock(&st->cmd_lock); + return ret; + } + + /* + * Flush samples currently in the FIFO, especially when the new latency + * is shorter than the old one: new timeout value is only considered when + * there is a new sample available. It can take a while for a slow + * sensor. + */ + st->param.cmd = MOTIONSENSE_CMD_FIFO_FLUSH; + ret = cros_ec_motion_send_host_cmd(st, 0); + mutex_unlock(&st->cmd_lock); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t cros_ec_sensor_get_report_latency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int latency, ret; + + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_EC_RATE; + st->param.ec_rate.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(st, 0); + latency = st->resp->ec_rate.ret; + mutex_unlock(&st->cmd_lock); + if (ret < 0) + return ret; + + return sprintf(buf, "%d.%06u\n", + latency / 1000, + (latency % 1000) * 1000); +} + +static IIO_DEVICE_ATTR(hwfifo_timeout, 0644, + cros_ec_sensor_get_report_latency, + cros_ec_sensor_set_report_latency, 0); + +static ssize_t hwfifo_watermark_max_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->fifo_max_event_count); +} + +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0); + +static const struct iio_dev_attr *cros_ec_sensor_fifo_attributes[] = { + &iio_dev_attr_hwfifo_timeout, + &iio_dev_attr_hwfifo_watermark_max, + NULL, +}; + +int cros_ec_sensors_push_data(struct iio_dev *indio_dev, + s16 *data, + s64 timestamp) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + s16 *out; + s64 delta; + unsigned int i; + + /* + * Ignore samples if the buffer is not set: it is needed if the ODR is + * set but the buffer is not enabled yet. + */ + if (!iio_device_try_claim_buffer_mode(indio_dev)) + return 0; + + out = (s16 *)st->samples; + iio_for_each_active_channel(indio_dev, i) { + *out = data[i]; + out++; + } + + if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME) + delta = iio_get_time_ns(indio_dev) - cros_ec_get_time_ns(); + else + delta = 0; + + iio_push_to_buffers_with_timestamp(indio_dev, st->samples, + timestamp + delta); + + iio_device_release_buffer_mode(indio_dev); + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_push_data); + +static void cros_ec_sensors_core_clean(void *arg) +{ + struct platform_device *pdev = (struct platform_device *)arg; + struct cros_ec_sensorhub *sensor_hub = + dev_get_drvdata(pdev->dev.parent); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + u8 sensor_num = st->param.info.sensor_num; + + cros_ec_sensorhub_unregister_push_data(sensor_hub, sensor_num); +} + +/** + * cros_ec_sensors_core_init() - basic initialization of the core structure + * @pdev: platform device created for the sensor + * @indio_dev: iio device structure of the device + * @physical_device: true if the device refers to a physical device + * @trigger_capture: function pointer to call buffer is triggered, + * for backward compatibility. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_core_init(struct platform_device *pdev, + struct iio_dev *indio_dev, + bool physical_device, + cros_ec_sensors_capture_t trigger_capture) +{ + struct device *dev = &pdev->dev; + struct cros_ec_sensors_core_state *state = iio_priv(indio_dev); + struct cros_ec_sensorhub *sensor_hub = dev_get_drvdata(dev->parent); + struct cros_ec_dev *ec = sensor_hub->ec; + struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); + u32 ver_mask, temp; + int frequencies[ARRAY_SIZE(state->frequencies) / 2] = { 0 }; + int ret, i; + + platform_set_drvdata(pdev, indio_dev); + + state->ec = ec->ec_dev; + state->msg = devm_kzalloc(&pdev->dev, sizeof(*state->msg) + + max((u16)sizeof(struct ec_params_motion_sense), + state->ec->max_response), GFP_KERNEL); + if (!state->msg) + return -ENOMEM; + + state->resp = (struct ec_response_motion_sense *)state->msg->data; + + mutex_init(&state->cmd_lock); + + ret = cros_ec_get_host_cmd_version_mask(state->ec, + ec->cmd_offset, + EC_CMD_MOTION_SENSE_CMD, + &ver_mask); + if (ret < 0) + return ret; + + /* Set up the host command structure. */ + state->msg->version = fls(ver_mask) - 1; + state->msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; + state->msg->outsize = sizeof(struct ec_params_motion_sense); + + indio_dev->name = pdev->name; + + if (physical_device) { + enum motionsensor_location loc; + + state->param.cmd = MOTIONSENSE_CMD_INFO; + state->param.info.sensor_num = sensor_platform->sensor_num; + ret = cros_ec_motion_send_host_cmd(state, 0); + if (ret) { + dev_warn(dev, "Can not access sensor info\n"); + return ret; + } + state->type = state->resp->info.type; + loc = state->resp->info.location; + if (loc == MOTIONSENSE_LOC_BASE) + indio_dev->label = "accel-base"; + else if (loc == MOTIONSENSE_LOC_LID) + indio_dev->label = "accel-display"; + else if (loc == MOTIONSENSE_LOC_CAMERA) + indio_dev->label = "accel-camera"; + + /* Set sign vector, only used for backward compatibility. */ + memset(state->sign, 1, CROS_EC_SENSOR_MAX_AXIS); + + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + state->calib[i].scale = MOTION_SENSE_DEFAULT_SCALE; + + /* 0 is a correct value used to stop the device */ + if (state->msg->version < 3) { + get_default_min_max_freq(state->resp->info.type, + &frequencies[1], + &frequencies[2], + &state->fifo_max_event_count); + } else { + if (state->resp->info_3.max_frequency == 0) { + get_default_min_max_freq(state->resp->info.type, + &frequencies[1], + &frequencies[2], + &temp); + } else { + frequencies[1] = state->resp->info_3.min_frequency; + frequencies[2] = state->resp->info_3.max_frequency; + } + state->fifo_max_event_count = state->resp->info_3.fifo_max_event_count; + } + for (i = 0; i < ARRAY_SIZE(frequencies); i++) { + state->frequencies[2 * i] = frequencies[i] / 1000; + state->frequencies[2 * i + 1] = + (frequencies[i] % 1000) * 1000; + } + + if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) { + /* + * Create a software buffer, feed by the EC FIFO. + * We can not use trigger here, as events are generated + * as soon as sample_frequency is set. + */ + ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, NULL, + cros_ec_sensor_fifo_attributes); + if (ret) + return ret; + + /* Timestamp coming from FIFO are in ns since boot. */ + ret = iio_device_set_clock(indio_dev, CLOCK_BOOTTIME); + if (ret) + return ret; + + } else { + /* + * The only way to get samples in buffer is to set a + * software trigger (systrig, hrtimer). + */ + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + NULL, trigger_capture, NULL); + if (ret) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_init); + +/** + * cros_ec_sensors_core_register() - Register callback to FIFO and IIO when + * sensor is ready. + * It must be called at the end of the sensor probe routine. + * @dev: device created for the sensor + * @indio_dev: iio device structure of the device + * @push_data: function to call when cros_ec_sensorhub receives + * a sample for that sensor. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_core_register(struct device *dev, + struct iio_dev *indio_dev, + cros_ec_sensorhub_push_data_cb_t push_data) +{ + struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); + struct cros_ec_sensorhub *sensor_hub = dev_get_drvdata(dev->parent); + struct platform_device *pdev = to_platform_device(dev); + struct cros_ec_dev *ec = sensor_hub->ec; + int ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + if (!push_data || + !cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) + return 0; + + ret = cros_ec_sensorhub_register_push_data( + sensor_hub, sensor_platform->sensor_num, + indio_dev, push_data); + if (ret) + return ret; + + return devm_add_action_or_reset( + dev, cros_ec_sensors_core_clean, pdev); +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_register); + +/** + * cros_ec_motion_send_host_cmd() - send motion sense host command + * @state: pointer to state information for device + * @opt_length: optional length to reduce the response size, useful on the data + * path. Otherwise, the maximal allowed response size is used + * + * When called, the sub-command is assumed to be set in param->cmd. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_motion_send_host_cmd(struct cros_ec_sensors_core_state *state, + u16 opt_length) +{ + struct ec_response_motion_sense *resp = (struct ec_response_motion_sense *)state->msg->data; + int ret; + + if (opt_length) + state->msg->insize = min(opt_length, state->ec->max_response); + else + state->msg->insize = state->ec->max_response; + + memcpy(state->msg->data, &state->param, sizeof(state->param)); + + ret = cros_ec_cmd_xfer_status(state->ec, state->msg); + trace_cros_ec_motion_host_cmd(&state->param, resp, ret); + if (ret < 0) + return ret; + + if (ret && state->resp != resp) + memcpy(state->resp, resp, ret); + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_motion_send_host_cmd); + +static ssize_t cros_ec_sensors_calibrate(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret, i; + bool calibrate; + + ret = kstrtobool(buf, &calibrate); + if (ret < 0) + return ret; + + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_PERFORM_CALIB; + st->param.perform_calib.enable = calibrate; + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret != 0) { + dev_warn(&indio_dev->dev, "Unable to calibrate sensor: %d\n", + ret); + } else { + /* Save values */ + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->calib[i].offset = st->resp->perform_calib.offset[i]; + } + mutex_unlock(&st->cmd_lock); + + return ret ? ret : len; +} + +static ssize_t cros_ec_sensors_id(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", st->param.info.sensor_num); +} + +const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[] = { + { + .name = "calibrate", + .shared = IIO_SHARED_BY_ALL, + .write = cros_ec_sensors_calibrate + }, + { + .name = "id", + .shared = IIO_SHARED_BY_ALL, + .read = cros_ec_sensors_id + }, + { } +}; +EXPORT_SYMBOL_GPL(cros_ec_sensors_ext_info); + +const struct iio_chan_spec_ext_info cros_ec_sensors_limited_info[] = { + { + .name = "id", + .shared = IIO_SHARED_BY_ALL, + .read = cros_ec_sensors_id + }, + { } +}; +EXPORT_SYMBOL_GPL(cros_ec_sensors_limited_info); + +/** + * cros_ec_sensors_idx_to_reg - convert index into offset in shared memory + * @st: pointer to state information for device + * @idx: sensor index (should be element of enum sensor_index) + * + * Return: address to read at + */ +static unsigned int cros_ec_sensors_idx_to_reg( + struct cros_ec_sensors_core_state *st, + unsigned int idx) +{ + /* + * When using LPC interface, only space for 2 Accel and one Gyro. + * First halfword of MOTIONSENSE_TYPE_ACCEL is used by angle. + */ + if (st->type == MOTIONSENSE_TYPE_ACCEL) + return EC_MEMMAP_ACC_DATA + sizeof(u16) * + (1 + idx + st->param.info.sensor_num * + CROS_EC_SENSOR_MAX_AXIS); + + return EC_MEMMAP_GYRO_DATA + sizeof(u16) * idx; +} + +static int cros_ec_sensors_cmd_read_u8(struct cros_ec_device *ec, + unsigned int offset, u8 *dest) +{ + return ec->cmd_readmem(ec, offset, 1, dest); +} + +static int cros_ec_sensors_cmd_read_u16(struct cros_ec_device *ec, + unsigned int offset, u16 *dest) +{ + __le16 tmp; + int ret = ec->cmd_readmem(ec, offset, 2, &tmp); + + if (ret >= 0) + *dest = le16_to_cpu(tmp); + + return ret; +} + +/** + * cros_ec_sensors_read_until_not_busy() - read until is not busy + * + * @st: pointer to state information for device + * + * Read from EC status byte until it reads not busy. + * Return: 8-bit status if ok, -errno on failure. + */ +static int cros_ec_sensors_read_until_not_busy( + struct cros_ec_sensors_core_state *st) +{ + struct cros_ec_device *ec = st->ec; + u8 status; + int ret, attempts = 0; + + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status); + if (ret < 0) + return ret; + + while (status & EC_MEMMAP_ACC_STATUS_BUSY_BIT) { + /* Give up after enough attempts, return error. */ + if (attempts++ >= 50) + return -EIO; + + /* Small delay every so often. */ + if (attempts % 5 == 0) + msleep(25); + + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, + &status); + if (ret < 0) + return ret; + } + + return status; +} + +/** + * cros_ec_sensors_read_data_unsafe() - read acceleration data from EC shared memory + * @indio_dev: pointer to IIO device + * @scan_mask: bitmap of the sensor indices to scan + * @data: location to store data + * + * This is the unsafe function for reading the EC data. It does not guarantee + * that the EC will not modify the data as it is being read in. + * + * Return: 0 on success, -errno on failure. + */ +static int cros_ec_sensors_read_data_unsafe(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + struct cros_ec_device *ec = st->ec; + unsigned int i; + int ret; + + /* Read all sensors enabled in scan_mask. Each value is 2 bytes. */ + for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) { + ret = cros_ec_sensors_cmd_read_u16(ec, + cros_ec_sensors_idx_to_reg(st, i), + data); + if (ret < 0) + return ret; + + *data *= st->sign[i]; + data++; + } + + return 0; +} + +/** + * cros_ec_sensors_read_lpc() - read acceleration data from EC shared memory. + * @indio_dev: pointer to IIO device. + * @scan_mask: bitmap of the sensor indices to scan. + * @data: location to store data. + * + * Note: this is the safe function for reading the EC data. It guarantees + * that the data sampled was not modified by the EC while being read. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_read_lpc(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + struct cros_ec_device *ec = st->ec; + u8 samp_id = 0xff, status = 0; + int ret, attempts = 0; + + /* + * Continually read all data from EC until the status byte after + * all reads reflects that the EC is not busy and the sample id + * matches the sample id from before all reads. This guarantees + * that data read in was not modified by the EC while reading. + */ + while ((status & (EC_MEMMAP_ACC_STATUS_BUSY_BIT | + EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK)) != samp_id) { + /* If we have tried to read too many times, return error. */ + if (attempts++ >= 5) + return -EIO; + + /* Read status byte until EC is not busy. */ + ret = cros_ec_sensors_read_until_not_busy(st); + if (ret < 0) + return ret; + + /* + * Store the current sample id so that we can compare to the + * sample id after reading the data. + */ + samp_id = ret & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; + + /* Read all EC data, format it, and store it into data. */ + ret = cros_ec_sensors_read_data_unsafe(indio_dev, scan_mask, + data); + if (ret < 0) + return ret; + + /* Read status byte. */ + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, + &status); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_read_lpc); + +/** + * cros_ec_sensors_read_cmd() - retrieve data using the EC command protocol + * @indio_dev: pointer to IIO device + * @scan_mask: bitmap of the sensor indices to scan + * @data: location to store data + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_read_cmd(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + unsigned int i; + + /* Read all sensor data through a command. */ + st->param.cmd = MOTIONSENSE_CMD_DATA; + ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->data)); + if (ret != 0) { + dev_warn(&indio_dev->dev, "Unable to read sensor data\n"); + return ret; + } + + for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) { + *data = st->resp->data.data[i]; + data++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_read_cmd); + +/** + * cros_ec_sensors_capture() - the trigger handler function + * @irq: the interrupt number. + * @p: a pointer to the poll function. + * + * On a trigger event occurring, if the pollfunc is attached then this + * handler is called as a threaded interrupt (and hence may sleep). It + * is responsible for grabbing data from the device and pushing it into + * the associated buffer. + * + * Return: IRQ_HANDLED + */ +irqreturn_t cros_ec_sensors_capture(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->cmd_lock); + + /* Clear capture data. */ + memset(st->samples, 0, indio_dev->scan_bytes); + + /* Read data based on which channels are enabled in scan mask. */ + ret = st->read_ec_sensors_data(indio_dev, + *(indio_dev->active_scan_mask), + (s16 *)st->samples); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->samples, + iio_get_time_ns(indio_dev)); + +done: + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + mutex_unlock(&st->cmd_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_capture); + +/** + * cros_ec_sensors_core_read() - function to request a value from the sensor + * @st: pointer to state information for device + * @chan: channel specification structure table + * @val: will contain one element making up the returned value + * @val2: will contain another element making up the returned value + * @mask: specifies which values to be requested + * + * Return: the type of value returned by the device + */ +int cros_ec_sensors_core_read(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret, frequency; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR; + st->param.sensor_odr.data = + EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret) + break; + + frequency = st->resp->sensor_odr.ret; + *val = frequency / 1000; + *val2 = (frequency % 1000) * 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read); + +/** + * cros_ec_sensors_core_read_avail() - get available values + * @indio_dev: pointer to state information for device + * @chan: channel specification structure table + * @vals: list of available values + * @type: type of data returned + * @length: number of data returned in the array + * @mask: specifies which values to be requested + * + * Return: an error code, IIO_AVAIL_RANGE or IIO_AVAIL_LIST + */ +int cros_ec_sensors_core_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, + int *length, + long mask) +{ + struct cros_ec_sensors_core_state *state = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(state->frequencies); + *vals = (const int *)&state->frequencies; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read_avail); + +/** + * cros_ec_sensors_core_write() - function to write a value to the sensor + * @st: pointer to state information for device + * @chan: channel specification structure table + * @val: first part of value to write + * @val2: second part of value to write + * @mask: specifies which values to write + * + * Return: the type of value returned by the device + */ +int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret, frequency; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + frequency = val * 1000 + val2 / 1000; + st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR; + st->param.sensor_odr.data = frequency; + + /* Always roundup, so caller gets at least what it asks for. */ + st->param.sensor_odr.roundup = 1; + + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret) + break; + + /* Flush the FIFO when a sensor is stopped. + * If the FIFO has just been emptied, pending samples will be + * stuck until new samples are available. It will not happen + * when all the sensors are stopped. + */ + if (frequency == 0) { + st->param.cmd = MOTIONSENSE_CMD_FIFO_FLUSH; + ret = cros_ec_motion_send_host_cmd(st, 0); + } + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write); + +static int __maybe_unused cros_ec_sensors_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret = 0; + + if (st->range_updated) { + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->param.sensor_range.data = st->curr_range; + st->param.sensor_range.roundup = 1; + ret = cros_ec_motion_send_host_cmd(st, 0); + mutex_unlock(&st->cmd_lock); + } + return ret; +} + +SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume); +EXPORT_SYMBOL_GPL(cros_ec_sensors_pm_ops); + +MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c new file mode 100644 index 000000000..c4db949fa --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +// Trace events for the ChromeOS Embedded Controller +// +// Copyright 2025 Google LLC. + +#define TRACE_SYMBOL(a) {a, #a} + +// Generate the list using the following script: +// sed -n 's/^.*\(MOTIONSENSE_CMD.*\) = .*,$/\tTRACE_SYMBOL(\1), \\/p' include/linux/platform_data/cros_ec_commands.h +#define MOTIONSENSE_CMDS \ + TRACE_SYMBOL(MOTIONSENSE_CMD_DUMP), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_INFO), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_EC_RATE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_ODR), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_RANGE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_KB_WAKE_ANGLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_DATA), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_INFO), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_FLUSH), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_READ), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_PERFORM_CALIB), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_OFFSET), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_LIST_ACTIVITIES), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SET_ACTIVITY), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_LID_ANGLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_INT_ENABLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SPOOF), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_TABLET_MODE_LID_ANGLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_SCALE) + +#define CREATE_TRACE_POINTS +#include "cros_ec_sensors_trace.h" diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h new file mode 100644 index 000000000..8956f2e8a --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Trace events for the ChromeOS Embedded Controller + * + * Copyright 2025 Google LLC. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cros_ec + +#if !defined(_CROS_EC_SENSORS_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _CROS_EC_SENSORS_TRACE_H_ + +#include +#include +#include +#include + +#include + +TRACE_EVENT(cros_ec_motion_host_cmd, + TP_PROTO(struct ec_params_motion_sense *param, + struct ec_response_motion_sense *resp, + int retval), + TP_ARGS(param, resp, retval), + TP_STRUCT__entry(__field(uint8_t, cmd) + __field(uint8_t, sensor_id) + __field(uint32_t, data) + __field(int, retval) + __field(int32_t, ret) + ), + TP_fast_assign(__entry->cmd = param->cmd; + __entry->sensor_id = param->sensor_odr.sensor_num; + __entry->data = param->sensor_odr.data; + __entry->retval = retval; + __entry->ret = retval > 0 ? resp->sensor_odr.ret : -1; + ), + TP_printk("%s, id: %d, data: %u, result: %u, return: %d", + __print_symbolic(__entry->cmd, MOTIONSENSE_CMDS), + __entry->sensor_id, + __entry->data, + __entry->retval, + __entry->ret) +); + +#endif /* _CROS_EC_SENSORS_TRACE_H_ */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH ../../drivers/iio/common/cros_ec_sensors + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cros_ec_sensors_trace + +#include diff --git a/drivers/iio/common/hid-sensors/Kconfig b/drivers/iio/common/hid-sensors/Kconfig new file mode 100644 index 000000000..2a3dd3b90 --- /dev/null +++ b/drivers/iio/common/hid-sensors/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Hid Sensor common modules +# +menu "Hid Sensor IIO Common" + +config HID_SENSOR_IIO_COMMON + tristate "Common modules for all HID Sensor IIO drivers" + depends on HID_SENSOR_HUB + select HID_SENSOR_IIO_TRIGGER if IIO_BUFFER + help + Say yes here to build support for HID sensor to use + HID sensor common processing for attributes and IIO triggers. + There are many attributes which can be shared among multiple + HID sensor drivers, this module contains processing for those + attributes. + +config HID_SENSOR_IIO_TRIGGER + tristate "Common module (trigger) for all HID Sensor IIO drivers" + depends on HID_SENSOR_HUB && HID_SENSOR_IIO_COMMON && IIO_BUFFER + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build trigger support for HID sensors. + Triggers will be send if all requested attributes were read. + + If this driver is compiled as a module, it will be named + hid-sensor-trigger. + +endmenu diff --git a/drivers/iio/common/hid-sensors/Makefile b/drivers/iio/common/hid-sensors/Makefile new file mode 100644 index 000000000..64b01a81f --- /dev/null +++ b/drivers/iio/common/hid-sensors/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Hid sensor common modules. +# + +obj-$(CONFIG_HID_SENSOR_IIO_COMMON) += hid-sensor-iio-common.o +obj-$(CONFIG_HID_SENSOR_IIO_TRIGGER) += hid-sensor-trigger.o +hid-sensor-iio-common-y := hid-sensor-attributes.o diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c new file mode 100644 index 000000000..9e7c26e37 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include +#include +#include +#include + +#include +#include + +static const 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}, + + {HID_USAGE_SENSOR_HUMAN_PRESENCE, 0, 1, 0}, + {HID_USAGE_SENSOR_HUMAN_PROXIMITY, 0, 1, 0}, + {HID_USAGE_SENSOR_HUMAN_ATTENTION, 0, 1, 0}, +}; + +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_NS(hid_sensor_read_poll_value, "IIO_HID_ATTRIBUTES"); + +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_NS(hid_sensor_read_samp_freq_value, "IIO_HID"); + +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_NS(hid_sensor_write_samp_freq_value, "IIO_HID"); + +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_NS(hid_sensor_read_raw_hyst_value, "IIO_HID"); + +int hid_sensor_read_raw_hyst_rel_value(struct hid_sensor_common *st, int *val1, + int *val2) +{ + s32 value; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->sensitivity_rel.report_id, + st->sensitivity_rel.index, + sizeof(value), &value); + if (ret < 0 || value < 0) { + *val1 = *val2 = 0; + return -EINVAL; + } + + convert_from_vtf_format(value, st->sensitivity_rel.size, + st->sensitivity_rel.unit_expo, val1, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL_NS(hid_sensor_read_raw_hyst_rel_value, "IIO_HID"); + +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_NS(hid_sensor_write_raw_hyst_value, "IIO_HID"); + +int hid_sensor_write_raw_hyst_rel_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_rel.size, + st->sensitivity_rel.unit_expo, + val1, val2); + ret = sensor_hub_set_feature(st->hsdev, st->sensitivity_rel.report_id, + st->sensitivity_rel.index, sizeof(value), + &value); + if (ret < 0 || value < 0) + return -EINVAL; + + ret = sensor_hub_get_feature(st->hsdev, + st->sensitivity_rel.report_id, + st->sensitivity_rel.index, + sizeof(value), &value); + if (ret < 0 || value < 0) + return -EINVAL; + + st->raw_hystersis = value; + + return 0; +} +EXPORT_SYMBOL_NS(hid_sensor_write_raw_hyst_rel_value, "IIO_HID"); + +/* + * This function 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_NS(hid_sensor_format_scale, "IIO_HID"); + +int64_t hid_sensor_convert_timestamp(struct hid_sensor_common *st, + int64_t raw_value) +{ + return st->timestamp_ns_scale * raw_value; +} +EXPORT_SYMBOL_NS(hid_sensor_convert_timestamp, "IIO_HID"); + +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_NS(hid_sensor_get_report_latency, "IIO_HID_ATTRIBUTES"); + +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_NS(hid_sensor_set_report_latency, "IIO_HID_ATTRIBUTES"); + +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_NS(hid_sensor_batch_mode_supported, "IIO_HID_ATTRIBUTES"); + +int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct hid_sensor_common *st, + const u32 *sensitivity_addresses, + u32 sensitivity_addresses_len) +{ + + struct hid_sensor_hub_attribute_info timestamp; + s32 value; + int ret; + int i; + + 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); + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_SENSITIVITY_REL_PCT, + &st->sensitivity_rel); + /* + * Set Sensitivity field ids, when there is no individual modifier, will + * check absolute sensitivity and relative sensitivity of data field + */ + for (i = 0; i < sensitivity_addresses_len; i++) { + if (st->sensitivity.index < 0) + sensor_hub_input_get_attribute_info( + hsdev, HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | + sensitivity_addresses[i], + &st->sensitivity); + + if (st->sensitivity_rel.index < 0) + sensor_hub_input_get_attribute_info( + hsdev, HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_REL_PCT | + sensitivity_addresses[i], + &st->sensitivity_rel); + } + + 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_NS(hid_sensor_parse_common_attributes, "IIO_HID"); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_DESCRIPTION("HID Sensor common attribute processing"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c new file mode 100644 index 000000000..60808e243 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid-sensor-trigger.h" + +static ssize_t _hid_sensor_set_report_latency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + int integer, fract, ret; + int latency; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + latency = integer * 1000 + fract / 1000; + ret = hid_sensor_set_report_latency(attrb, latency); + if (ret < 0) + return ret; + + attrb->latency_ms = hid_sensor_get_report_latency(attrb); + + return len; +} + +static ssize_t _hid_sensor_get_report_latency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + int latency; + + latency = hid_sensor_get_report_latency(attrb); + if (latency < 0) + return latency; + + return sprintf(buf, "%d.%06u\n", latency / 1000, (latency % 1000) * 1000); +} + +static ssize_t _hid_sensor_get_fifo_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + int latency; + + latency = hid_sensor_get_report_latency(attrb); + if (latency < 0) + return latency; + + return sprintf(buf, "%d\n", !!latency); +} + +static IIO_DEVICE_ATTR(hwfifo_timeout, 0644, + _hid_sensor_get_report_latency, + _hid_sensor_set_report_latency, 0); +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, + _hid_sensor_get_fifo_state, NULL, 0); + +static const struct iio_dev_attr *hid_sensor_fifo_attributes[] = { + &iio_dev_attr_hwfifo_timeout, + &iio_dev_attr_hwfifo_enabled, + NULL, +}; + +static int _hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ + int state_val; + int report_val; + s32 poll_value = 0; + + if (state) { + if (sensor_hub_device_open(st->hsdev)) + return -EIO; + + atomic_inc(&st->data_ready); + + state_val = hid_sensor_get_usage_index(st->hsdev, + st->power_state.report_id, + st->power_state.index, + HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM); + report_val = hid_sensor_get_usage_index(st->hsdev, + st->report_state.report_id, + st->report_state.index, + HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM); + + poll_value = hid_sensor_read_poll_value(st); + } else { + int val; + + val = atomic_dec_if_positive(&st->data_ready); + if (val < 0) + return 0; + + sensor_hub_device_close(st->hsdev); + state_val = hid_sensor_get_usage_index(st->hsdev, + st->power_state.report_id, + st->power_state.index, + HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM); + report_val = hid_sensor_get_usage_index(st->hsdev, + st->report_state.report_id, + st->report_state.index, + HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM); + } + + 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, 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, + sizeof(report_val), + &report_val); + } + + pr_debug("HID_SENSOR %s set power_state %d report_state %d\n", + st->pdev->name, state_val, report_val); + + sensor_hub_get_feature(st->hsdev, st->power_state.report_id, + st->power_state.index, + sizeof(state_val), &state_val); + if (state && poll_value) + msleep_interruptible(poll_value * 2); + + return 0; +} +EXPORT_SYMBOL_NS(hid_sensor_power_state, "IIO_HID"); + +int hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ + +#ifdef CONFIG_PM + int ret; + + if (atomic_add_unless(&st->runtime_pm_enable, 1, 1)) + pm_runtime_enable(&st->pdev->dev); + + if (state) { + atomic_inc(&st->user_requested_state); + ret = pm_runtime_resume_and_get(&st->pdev->dev); + } else { + atomic_dec(&st->user_requested_state); + pm_runtime_use_autosuspend(&st->pdev->dev); + ret = pm_runtime_put_autosuspend(&st->pdev->dev); + } + if (ret < 0) + return ret; + + return 0; +#else + atomic_set(&st->user_requested_state, state); + return _hid_sensor_power_state(st, state); +#endif +} + +static void hid_sensor_set_power_work(struct work_struct *work) +{ + struct hid_sensor_common *attrb = container_of(work, + struct hid_sensor_common, + work); + + if (attrb->poll_interval >= 0) + sensor_hub_set_feature(attrb->hsdev, attrb->poll.report_id, + attrb->poll.index, + sizeof(attrb->poll_interval), + &attrb->poll_interval); + + if (attrb->raw_hystersis >= 0) + sensor_hub_set_feature(attrb->hsdev, + attrb->sensitivity.report_id, + attrb->sensitivity.index, + sizeof(attrb->raw_hystersis), + &attrb->raw_hystersis); + + if (attrb->latency_ms > 0) + hid_sensor_set_report_latency(attrb, attrb->latency_ms); + + if (atomic_read(&attrb->user_requested_state)) + _hid_sensor_power_state(attrb, true); +} + +static int buffer_postenable(struct iio_dev *indio_dev) +{ + return hid_sensor_power_state(iio_device_get_drvdata(indio_dev), 1); +} + +static int buffer_predisable(struct iio_dev *indio_dev) +{ + return hid_sensor_power_state(iio_device_get_drvdata(indio_dev), 0); +} + +static const struct iio_buffer_setup_ops hid_sensor_buffer_ops = { + .postenable = buffer_postenable, + .predisable = buffer_predisable, +}; + +void hid_sensor_remove_trigger(struct iio_dev *indio_dev, + struct hid_sensor_common *attrb) +{ + if (atomic_read(&attrb->runtime_pm_enable)) + pm_runtime_disable(&attrb->pdev->dev); + + pm_runtime_set_suspended(&attrb->pdev->dev); + + cancel_work_sync(&attrb->work); + iio_trigger_unregister(attrb->trigger); + iio_trigger_free(attrb->trigger); +} +EXPORT_SYMBOL_NS(hid_sensor_remove_trigger, "IIO_HID"); + +int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, + struct hid_sensor_common *attrb) +{ + const struct iio_dev_attr **fifo_attrs; + int ret; + struct iio_trigger *trig; + + if (hid_sensor_batch_mode_supported(attrb)) + fifo_attrs = hid_sensor_fifo_attributes; + else + fifo_attrs = NULL; + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_HARDWARE_TRIGGERED; + + ret = devm_iio_kfifo_buffer_setup_ext(&indio_dev->dev, indio_dev, + &hid_sensor_buffer_ops, + fifo_attrs); + if (ret) { + dev_err(&indio_dev->dev, "Kfifo Buffer Setup Failed\n"); + return ret; + } + + /* + * The current user space in distro "iio-sensor-proxy" is not working in + * trigerless mode and it expects + * /sys/bus/iio/devices/iio:device0/trigger/current_trigger. + * The change replacing iio_triggered_buffer_setup_ext() with + * devm_iio_kfifo_buffer_setup_ext() will not create attribute without + * registering a trigger with INDIO_HARDWARE_TRIGGERED. + * So the below code fragment is still required. + */ + + trig = iio_trigger_alloc(indio_dev->dev.parent, + "%s-dev%d", name, iio_device_id(indio_dev)); + if (!trig) { + dev_err(&indio_dev->dev, "Trigger Allocate Failed\n"); + return -ENOMEM; + } + + iio_trigger_set_drvdata(trig, attrb); + ret = iio_trigger_register(trig); + + if (ret) { + dev_err(&indio_dev->dev, "Trigger Register Failed\n"); + goto error_free_trig; + } + attrb->trigger = trig; + indio_dev->trig = iio_trigger_get(trig); + + ret = pm_runtime_set_active(&indio_dev->dev); + if (ret) + goto error_unreg_trigger; + + iio_device_set_drvdata(indio_dev, attrb); + + INIT_WORK(&attrb->work, hid_sensor_set_power_work); + + pm_suspend_ignore_children(&attrb->pdev->dev, true); + /* Default to 3 seconds, but can be changed from sysfs */ + pm_runtime_set_autosuspend_delay(&attrb->pdev->dev, + 3000); + return ret; +error_unreg_trigger: + iio_trigger_unregister(trig); +error_free_trig: + iio_trigger_free(trig); + return ret; +} +EXPORT_SYMBOL_NS(hid_sensor_setup_trigger, "IIO_HID"); + +static int __maybe_unused hid_sensor_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, false); +} + +static int __maybe_unused hid_sensor_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + schedule_work(&attrb->work); + + return 0; +} + +static int __maybe_unused hid_sensor_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, true); +} + +const struct dev_pm_ops hid_sensor_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(hid_sensor_suspend, hid_sensor_resume) + SET_RUNTIME_PM_OPS(hid_sensor_suspend, + hid_sensor_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS(hid_sensor_pm_ops, "IIO_HID"); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_DESCRIPTION("HID Sensor trigger processing"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID_ATTRIBUTES"); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h new file mode 100644 index 000000000..589de858e --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#ifndef _HID_SENSOR_TRIGGER_H +#define _HID_SENSOR_TRIGGER_H + +#include +#include + +struct hid_sensor_common; +struct iio_dev; + +extern const struct dev_pm_ops hid_sensor_pm_ops; + +int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, + struct hid_sensor_common *attrb); +void hid_sensor_remove_trigger(struct iio_dev *indio_dev, + struct hid_sensor_common *attrb); +int hid_sensor_power_state(struct hid_sensor_common *st, bool state); + +#endif diff --git a/drivers/iio/common/inv_sensors/Kconfig b/drivers/iio/common/inv_sensors/Kconfig new file mode 100644 index 000000000..28815fb43 --- /dev/null +++ b/drivers/iio/common/inv_sensors/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# TDK-InvenSense sensors common library +# + +config IIO_INV_SENSORS_TIMESTAMP + tristate diff --git a/drivers/iio/common/inv_sensors/Makefile b/drivers/iio/common/inv_sensors/Makefile new file mode 100644 index 000000000..dcf39f249 --- /dev/null +++ b/drivers/iio/common/inv_sensors/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for TDK-InvenSense sensors module. +# + +obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_sensors_timestamp.o diff --git a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c new file mode 100644 index 000000000..88a82d137 --- /dev/null +++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include +#include +#include +#include + +#include + +/* compute min and max following jitter in per mille */ +#define INV_SENSORS_TIMESTAMP_MIN(_val, _jitter) \ + (((_val) * (1000 - (_jitter))) / 1000) +#define INV_SENSORS_TIMESTAMP_MAX(_val, _jitter) \ + (((_val) * (1000 + (_jitter))) / 1000) + +/* minimum timestamp delta between 2 interrupts for measuring period (20ms) */ +#define INV_SENSORS_MIN_IT_DELTA (20 * NSEC_PER_MSEC) + +/* Add a new value inside an accumulator and update the estimate value */ +static void inv_update_acc(struct inv_sensors_timestamp_acc *acc, u32 val) +{ + u64 sum = 0; + size_t i; + + acc->values[acc->idx++] = val; + if (acc->idx >= ARRAY_SIZE(acc->values)) + acc->idx = 0; + + /* compute the mean of all stored values, use 0 as empty slot */ + for (i = 0; i < ARRAY_SIZE(acc->values); ++i) { + if (acc->values[i] == 0) + break; + sum += acc->values[i]; + } + + acc->val = div_u64(sum, i); +} + +void inv_sensors_timestamp_init(struct inv_sensors_timestamp *ts, + const struct inv_sensors_timestamp_chip *chip) +{ + memset(ts, 0, sizeof(*ts)); + + /* save chip parameters and compute min and max clock period */ + ts->chip = *chip; + ts->min_period = INV_SENSORS_TIMESTAMP_MIN(chip->clock_period, chip->jitter); + ts->max_period = INV_SENSORS_TIMESTAMP_MAX(chip->clock_period, chip->jitter); + + /* current multiplier and period values after reset */ + ts->mult = chip->init_period / chip->clock_period; + ts->period = chip->init_period; + + /* use theoretical value for chip period */ + inv_update_acc(&ts->chip_period, chip->clock_period); +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_init, "IIO_INV_SENSORS_TIMESTAMP"); + +int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts, + u32 period, bool fifo) +{ + u32 mult; + + /* when FIFO is on, prevent odr change if one is already pending */ + if (fifo && ts->new_mult != 0) + return -EAGAIN; + + mult = period / ts->chip.clock_period; + if (mult != ts->mult) + ts->new_mult = mult; + + /* When FIFO is off, directly apply the new ODR */ + if (!fifo) + inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, "IIO_INV_SENSORS_TIMESTAMP"); + +static bool inv_validate_period(struct inv_sensors_timestamp *ts, u32 period) +{ + u32 period_min, period_max; + + /* check that period is acceptable */ + period_min = ts->min_period * ts->mult; + period_max = ts->max_period * ts->mult; + if (period > period_min && period < period_max) + return true; + else + return false; +} + +static bool inv_update_chip_period(struct inv_sensors_timestamp *ts, + u32 period) +{ + u32 new_chip_period; + + if (!inv_validate_period(ts, period)) + return false; + + /* update chip internal period estimation */ + new_chip_period = period / ts->mult; + inv_update_acc(&ts->chip_period, new_chip_period); + + return true; +} + +static u32 inv_align_timestamp_it(struct inv_sensors_timestamp *ts, + unsigned int sample_nb) +{ + const s64 period_min = (s64)ts->min_period * ts->mult; + const s64 period_max = (s64)ts->max_period * ts->mult; + s64 new_period; + + /* compute new period aligning last timestamp with interrupt timestamp */ + new_period = div_s64(ts->it.up - ts->timestamp, sample_nb); + + /* ensure that period never overflows the jitter */ + return clamp(new_period, period_min, period_max); +} + +void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, + size_t sample_nb, s64 timestamp) +{ + struct inv_sensors_timestamp_interval *it; + s64 delta, delta_threshold, interval; + u32 period; + bool valid = false; + + if (sample_nb == 0) + return; + + /* no previous data, compute theoretical value from interrupt */ + if (ts->timestamp == 0) { + /* elapsed time: sensor period * sensor samples number */ + interval = (s64)ts->period * (s64)sample_nb; + ts->timestamp = timestamp - interval; + } + + /* update delta timestamps and estimated period */ + it = &ts->delta; + ts->delta_counter += sample_nb; + delta = timestamp - it->up; + delta_threshold = INV_SENSORS_TIMESTAMP_MIN(INV_SENSORS_MIN_IT_DELTA, ts->chip.jitter); + if (delta >= delta_threshold) { + it->lo = it->up; + it->up = timestamp; + if (it->lo != 0) { + /* compute period: delta time divided by number of samples */ + delta = it->up - it->lo; + period = div_s64(delta, ts->delta_counter); + inv_update_chip_period(ts, period); + } + ts->delta_counter = 0; + } + + /* update interrupt timestamp and compute chip and sensor periods */ + it = &ts->it; + it->lo = it->up; + it->up = timestamp; + delta = it->up - it->lo; + if (it->lo != 0) { + /* compute period and check validity */ + period = div_s64(delta, sample_nb); + valid = inv_validate_period(ts, period); + } + + /* if interrupt interval is valid, sync with interrupt timestamp */ + if (valid) + ts->period = inv_align_timestamp_it(ts, sample_nb); + else + ts->period = ts->mult * ts->chip_period.val; +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_interrupt, "IIO_INV_SENSORS_TIMESTAMP"); + +void inv_sensors_timestamp_apply_odr(struct inv_sensors_timestamp *ts, + u32 fifo_period, size_t fifo_nb, + unsigned int fifo_no) +{ + s64 interval; + u32 fifo_mult; + + if (ts->new_mult == 0) + return; + + /* update to new multiplier and update period */ + ts->mult = ts->new_mult; + ts->new_mult = 0; + ts->period = ts->mult * ts->chip_period.val; + + /* + * After ODR change the time interval with the previous sample is + * undertermined (depends when the change occurs). So we compute the + * timestamp from the current interrupt using the new FIFO period, the + * total number of samples and the current sample numero. + */ + if (ts->timestamp != 0) { + /* compute measured fifo period */ + fifo_mult = fifo_period / ts->chip.clock_period; + fifo_period = fifo_mult * ts->chip_period.val; + /* computes time interval between interrupt and this sample */ + interval = (s64)(fifo_nb - fifo_no) * (s64)fifo_period; + ts->timestamp = ts->it.up - interval; + } +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_apply_odr, "IIO_INV_SENSORS_TIMESTAMP"); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense sensors timestamp module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ms_sensors/Kconfig b/drivers/iio/common/ms_sensors/Kconfig new file mode 100644 index 000000000..45012b7ad --- /dev/null +++ b/drivers/iio/common/ms_sensors/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Measurements Specialties sensors common library +# + +config IIO_MS_SENSORS_I2C + tristate diff --git a/drivers/iio/common/ms_sensors/Makefile b/drivers/iio/common/ms_sensors/Makefile new file mode 100644 index 000000000..028573b9b --- /dev/null +++ b/drivers/iio/common/ms_sensors/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Measurement Specialties sensor common modules. +# + +obj-$(CONFIG_IIO_MS_SENSORS_I2C) += ms_sensors_i2c.o diff --git a/drivers/iio/common/ms_sensors/ms_sensors_i2c.c b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c new file mode 100644 index 000000000..1960a2ce8 --- /dev/null +++ b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c @@ -0,0 +1,697 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Measurements Specialties driver common i2c functions + * + * Copyright (c) 2015 Measurement-Specialties + */ + +#include +#include +#include +#include + +#include "ms_sensors_i2c.h" + +/* Conversion times in us */ +static const u16 ms_sensors_ht_t_conversion_time[] = { 50000, 25000, + 13000, 7000 }; +static const u16 ms_sensors_ht_h_conversion_time[] = { 16000, 5000, + 3000, 8000 }; +static const u16 ms_sensors_tp_conversion_time[] = { 500, 1100, 2100, + 4100, 8220, 16440 }; + +#define MS_SENSORS_SERIAL_READ_MSB 0xFA0F +#define MS_SENSORS_SERIAL_READ_LSB 0xFCC9 +#define MS_SENSORS_CONFIG_REG_WRITE 0xE6 +#define MS_SENSORS_CONFIG_REG_READ 0xE7 +#define MS_SENSORS_HT_T_CONVERSION_START 0xF3 +#define MS_SENSORS_HT_H_CONVERSION_START 0xF5 + +#define MS_SENSORS_TP_PROM_READ 0xA0 +#define MS_SENSORS_TP_T_CONVERSION_START 0x50 +#define MS_SENSORS_TP_P_CONVERSION_START 0x40 +#define MS_SENSORS_TP_ADC_READ 0x00 + +#define MS_SENSORS_NO_READ_CMD 0xFF + +/** + * ms_sensors_reset() - Reset function + * @cli: pointer to device client + * @cmd: reset cmd. Depends on device in use + * @delay: usleep minimal delay after reset command is issued + * + * Generic I2C reset function for Measurement Specialties devices. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_reset(void *cli, u8 cmd, unsigned int delay) +{ + int ret; + struct i2c_client *client = cli; + + ret = i2c_smbus_write_byte(client, cmd); + if (ret) { + dev_err(&client->dev, "Failed to reset device\n"); + return ret; + } + usleep_range(delay, delay + 1000); + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_reset, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_read_prom_word() - PROM word read function + * @cli: pointer to device client + * @cmd: PROM read cmd. Depends on device and prom id + * @word: pointer to word destination value + * + * Generic i2c prom word read function for Measurement Specialties devices. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_read_prom_word(void *cli, int cmd, u16 *word) +{ + int ret; + struct i2c_client *client = cli; + + ret = i2c_smbus_read_word_swapped(client, cmd); + if (ret < 0) { + dev_err(&client->dev, "Failed to read prom word\n"); + return ret; + } + *word = ret; + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_read_prom_word, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_convert_and_read() - ADC conversion & read function + * @cli: pointer to device client + * @conv: ADC conversion command. Depends on device in use + * @rd: ADC read command. Depends on device in use + * @delay: usleep minimal delay after conversion command is issued + * @adc: pointer to ADC destination value + * + * Generic ADC conversion & read function for Measurement Specialties + * devices. + * The function will issue conversion command, sleep appropriate delay, and + * issue command to read ADC. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_convert_and_read(void *cli, u8 conv, u8 rd, + unsigned int delay, u32 *adc) +{ + int ret; + __be32 buf = 0; + struct i2c_client *client = cli; + + /* Trigger conversion */ + ret = i2c_smbus_write_byte(client, conv); + if (ret) + goto err; + usleep_range(delay, delay + 1000); + + /* Retrieve ADC value */ + if (rd != MS_SENSORS_NO_READ_CMD) + ret = i2c_smbus_read_i2c_block_data(client, rd, 3, (u8 *)&buf); + else + ret = i2c_master_recv(client, (u8 *)&buf, 3); + if (ret < 0) + goto err; + + dev_dbg(&client->dev, "ADC raw value : %x\n", be32_to_cpu(buf) >> 8); + *adc = be32_to_cpu(buf) >> 8; + + return 0; +err: + dev_err(&client->dev, "Unable to make sensor adc conversion\n"); + return ret; +} +EXPORT_SYMBOL_NS(ms_sensors_convert_and_read, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_crc_valid() - CRC check function + * @value: input and CRC compare value + * + * Cyclic Redundancy Check function used in TSYS02D, HTU21, MS8607. + * This function performs a x^8 + x^5 + x^4 + 1 polynomial CRC. + * The argument contains CRC value in LSB byte while the bytes 1 and 2 + * are used for CRC computation. + * + * Return: 1 if CRC is valid, 0 otherwise. + */ +static bool ms_sensors_crc_valid(u32 value) +{ + u32 polynom = 0x988000; /* x^8 + x^5 + x^4 + 1 */ + u32 msb = 0x800000; + u32 mask = 0xFF8000; + u32 result = value & 0xFFFF00; + u8 crc = value & 0xFF; + + while (msb != 0x80) { + if (result & msb) + result = ((result ^ polynom) & mask) + | (result & ~mask); + msb >>= 1; + mask >>= 1; + polynom >>= 1; + } + + return result == crc; +} + +/** + * ms_sensors_read_serial() - Serial number read function + * @client: pointer to i2c client + * @sn: pointer to 64-bits destination value + * + * Generic i2c serial number read function for Measurement Specialties devices. + * This function is used for TSYS02d, HTU21, MS8607 chipset. + * Refer to datasheet: + * http://www.meas-spec.com/downloads/HTU2X_Serial_Number_Reading.pdf + * + * Sensor raw MSB serial number format is the following : + * [ SNB3, CRC, SNB2, CRC, SNB1, CRC, SNB0, CRC] + * Sensor raw LSB serial number format is the following : + * [ X, X, SNC1, SNC0, CRC, SNA1, SNA0, CRC] + * The resulting serial number is following : + * [ SNA1, SNA0, SNB3, SNB2, SNB1, SNB0, SNC1, SNC0] + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_read_serial(struct i2c_client *client, u64 *sn) +{ + u8 i; + __be64 rcv_buf = 0; + u64 rcv_val; + __be16 send_buf; + int ret; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = client->flags, + .len = 2, + .buf = (__u8 *)&send_buf, + }, + { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .buf = (__u8 *)&rcv_buf, + }, + }; + + /* Read MSB part of serial number */ + send_buf = cpu_to_be16(MS_SENSORS_SERIAL_READ_MSB); + msg[1].len = 8; + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, "Unable to read device serial number"); + return ret; + } + + rcv_val = be64_to_cpu(rcv_buf); + dev_dbg(&client->dev, "Serial MSB raw : %llx\n", rcv_val); + + for (i = 0; i < 64; i += 16) { + if (!ms_sensors_crc_valid((rcv_val >> i) & 0xFFFF)) + return -ENODEV; + } + + *sn = (((rcv_val >> 32) & 0xFF000000) | + ((rcv_val >> 24) & 0x00FF0000) | + ((rcv_val >> 16) & 0x0000FF00) | + ((rcv_val >> 8) & 0x000000FF)) << 16; + + /* Read LSB part of serial number */ + send_buf = cpu_to_be16(MS_SENSORS_SERIAL_READ_LSB); + msg[1].len = 6; + rcv_buf = 0; + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, "Unable to read device serial number"); + return ret; + } + + rcv_val = be64_to_cpu(rcv_buf) >> 16; + dev_dbg(&client->dev, "Serial MSB raw : %llx\n", rcv_val); + + for (i = 0; i < 48; i += 24) { + if (!ms_sensors_crc_valid((rcv_val >> i) & 0xFFFFFF)) + return -ENODEV; + } + + *sn |= (rcv_val & 0xFFFF00) << 40 | (rcv_val >> 32); + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_read_serial, "IIO_MEAS_SPEC_SENSORS"); + +static int ms_sensors_read_config_reg(struct i2c_client *client, + u8 *config_reg) +{ + int ret; + + ret = i2c_smbus_write_byte(client, MS_SENSORS_CONFIG_REG_READ); + if (ret) { + dev_err(&client->dev, "Unable to read config register"); + return ret; + } + + ret = i2c_master_recv(client, config_reg, 1); + if (ret < 0) { + dev_err(&client->dev, "Unable to read config register"); + return ret; + } + dev_dbg(&client->dev, "Config register :%x\n", *config_reg); + + return 0; +} + +/** + * ms_sensors_write_resolution() - Set resolution function + * @dev_data: pointer to temperature/humidity device data + * @i: resolution index to set + * + * This function will program the appropriate resolution based on the index + * provided when user space will set samp_freq channel. + * This function is used for TSYS02D, HTU21 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +ssize_t ms_sensors_write_resolution(struct ms_ht_dev *dev_data, + u8 i) +{ + u8 config_reg; + int ret; + + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + if (ret) + return ret; + + config_reg &= 0x7E; + config_reg |= ((i & 1) << 7) + ((i & 2) >> 1); + + return i2c_smbus_write_byte_data(dev_data->client, + MS_SENSORS_CONFIG_REG_WRITE, + config_reg); +} +EXPORT_SYMBOL_NS(ms_sensors_write_resolution, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_show_battery_low() - Show device battery low indicator + * @dev_data: pointer to temperature/humidity device data + * @buf: pointer to char buffer to write result + * + * This function will read battery indicator value in the device and + * return 1 if the device voltage is below 2.25V. + * This function is used for TSYS02D, HTU21 and MS8607 chipsets. + * + * Return: length of sprintf on success, negative errno otherwise. + */ +ssize_t ms_sensors_show_battery_low(struct ms_ht_dev *dev_data, + char *buf) +{ + int ret; + u8 config_reg; + + mutex_lock(&dev_data->lock); + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", (config_reg & 0x40) >> 6); +} +EXPORT_SYMBOL_NS(ms_sensors_show_battery_low, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_show_heater() - Show device heater + * @dev_data: pointer to temperature/humidity device data + * @buf: pointer to char buffer to write result + * + * This function will read heater enable value in the device and + * return 1 if the heater is enabled. + * This function is used for HTU21 and MS8607 chipsets. + * + * Return: length of sprintf on success, negative errno otherwise. + */ +ssize_t ms_sensors_show_heater(struct ms_ht_dev *dev_data, + char *buf) +{ + u8 config_reg; + int ret; + + mutex_lock(&dev_data->lock); + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", (config_reg & 0x4) >> 2); +} +EXPORT_SYMBOL_NS(ms_sensors_show_heater, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_write_heater() - Write device heater + * @dev_data: pointer to temperature/humidity device data + * @buf: pointer to char buffer from user space + * @len: length of buf + * + * This function will write 1 or 0 value in the device + * to enable or disable heater. + * This function is used for HTU21 and MS8607 chipsets. + * + * Return: length of buffer, negative errno otherwise. + */ +ssize_t ms_sensors_write_heater(struct ms_ht_dev *dev_data, + const char *buf, size_t len) +{ + u8 val, config_reg; + int ret; + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 1) + return -EINVAL; + + mutex_lock(&dev_data->lock); + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + if (ret) { + mutex_unlock(&dev_data->lock); + return ret; + } + + config_reg &= 0xFB; + config_reg |= val << 2; + + ret = i2c_smbus_write_byte_data(dev_data->client, + MS_SENSORS_CONFIG_REG_WRITE, + config_reg); + mutex_unlock(&dev_data->lock); + if (ret) { + dev_err(&dev_data->client->dev, "Unable to write config register\n"); + return ret; + } + + return len; +} +EXPORT_SYMBOL_NS(ms_sensors_write_heater, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_ht_read_temperature() - Read temperature + * @dev_data: pointer to temperature/humidity device data + * @temperature:pointer to temperature destination value + * + * This function will get temperature ADC value from the device, + * check the CRC and compute the temperature value. + * This function is used for TSYS02D, HTU21 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_ht_read_temperature(struct ms_ht_dev *dev_data, + s32 *temperature) +{ + int ret; + u32 adc; + u16 delay; + + mutex_lock(&dev_data->lock); + delay = ms_sensors_ht_t_conversion_time[dev_data->res_index]; + ret = ms_sensors_convert_and_read(dev_data->client, + MS_SENSORS_HT_T_CONVERSION_START, + MS_SENSORS_NO_READ_CMD, + delay, &adc); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + if (!ms_sensors_crc_valid(adc)) { + dev_err(&dev_data->client->dev, + "Temperature read crc check error\n"); + return -ENODEV; + } + + /* Temperature algorithm */ + *temperature = (((s64)(adc >> 8) * 175720) >> 16) - 46850; + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_ht_read_temperature, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_ht_read_humidity() - Read humidity + * @dev_data: pointer to temperature/humidity device data + * @humidity: pointer to humidity destination value + * + * This function will get humidity ADC value from the device, + * check the CRC and compute the temperature value. + * This function is used for HTU21 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_ht_read_humidity(struct ms_ht_dev *dev_data, + u32 *humidity) +{ + int ret; + u32 adc; + u16 delay; + + mutex_lock(&dev_data->lock); + delay = ms_sensors_ht_h_conversion_time[dev_data->res_index]; + ret = ms_sensors_convert_and_read(dev_data->client, + MS_SENSORS_HT_H_CONVERSION_START, + MS_SENSORS_NO_READ_CMD, + delay, &adc); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + if (!ms_sensors_crc_valid(adc)) { + dev_err(&dev_data->client->dev, + "Humidity read crc check error\n"); + return -ENODEV; + } + + /* Humidity algorithm */ + *humidity = (((s32)(adc >> 8) * 12500) >> 16) * 10 - 6000; + if (*humidity >= 100000) + *humidity = 100000; + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_ht_read_humidity, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_tp_crc4() - Calculate PROM CRC for + * Temperature and pressure devices. + * This function is only used when reading PROM coefficients + * + * @prom: pointer to PROM coefficients array + * + * Return: CRC. + */ +static u8 ms_sensors_tp_crc4(u16 *prom) +{ + unsigned int cnt, n_bit; + u16 n_rem = 0x0000; + + for (cnt = 0; cnt < MS_SENSORS_TP_PROM_WORDS_NB * 2; cnt++) { + if (cnt % 2 == 1) + n_rem ^= prom[cnt >> 1] & 0x00FF; + else + n_rem ^= prom[cnt >> 1] >> 8; + + for (n_bit = 8; n_bit > 0; n_bit--) { + if (n_rem & 0x8000) + n_rem = (n_rem << 1) ^ 0x3000; + else + n_rem <<= 1; + } + } + + return n_rem >> 12; +} + +/** + * ms_sensors_tp_crc_valid_112() - CRC check function for + * Temperature and pressure devices for 112bit PROM. + * This function is only used when reading PROM coefficients + * + * @prom: pointer to PROM coefficients array + * + * Return: True if CRC is ok. + */ +static bool ms_sensors_tp_crc_valid_112(u16 *prom) +{ + u16 w0 = prom[0], crc_read = (w0 & 0xF000) >> 12; + u8 crc; + + prom[0] &= 0x0FFF; /* Clear the CRC computation part */ + prom[MS_SENSORS_TP_PROM_WORDS_NB - 1] = 0; + + crc = ms_sensors_tp_crc4(prom); + + prom[0] = w0; + + return crc == crc_read; +} + +/** + * ms_sensors_tp_crc_valid_128() - CRC check function for + * Temperature and pressure devices for 128bit PROM. + * This function is only used when reading PROM coefficients + * + * @prom: pointer to PROM coefficients array + * + * Return: True if CRC is ok. + */ +static bool ms_sensors_tp_crc_valid_128(u16 *prom) +{ + u16 w7 = prom[7], crc_read = w7 & 0x000F; + u8 crc; + + prom[7] &= 0xFF00; /* Clear the CRC and LSB part */ + + crc = ms_sensors_tp_crc4(prom); + + prom[7] = w7; + + return crc == crc_read; +} + +/** + * ms_sensors_tp_read_prom() - prom coeff read function + * @dev_data: pointer to temperature/pressure device data + * + * This function will read prom coefficients and check CRC. + * This function is used for MS5637 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_tp_read_prom(struct ms_tp_dev *dev_data) +{ + int i, ret; + bool valid; + + for (i = 0; i < dev_data->hw->prom_len; i++) { + ret = ms_sensors_read_prom_word( + dev_data->client, + MS_SENSORS_TP_PROM_READ + (i << 1), + &dev_data->prom[i]); + + if (ret) + return ret; + } + + if (dev_data->hw->prom_len == 8) + valid = ms_sensors_tp_crc_valid_128(dev_data->prom); + else + valid = ms_sensors_tp_crc_valid_112(dev_data->prom); + + if (!valid) { + dev_err(&dev_data->client->dev, + "Calibration coefficients crc check error\n"); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_tp_read_prom, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_read_temp_and_pressure() - read temp and pressure + * @dev_data: pointer to temperature/pressure device data + * @temperature:pointer to temperature destination value + * @pressure: pointer to pressure destination value + * + * This function will read ADC and compute pressure and temperature value. + * This function is used for MS5637 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_read_temp_and_pressure(struct ms_tp_dev *dev_data, + int *temperature, + unsigned int *pressure) +{ + int ret; + u32 t_adc, p_adc; + s32 dt, temp; + s64 off, sens, t2, off2, sens2; + u16 *prom = dev_data->prom, delay; + + mutex_lock(&dev_data->lock); + delay = ms_sensors_tp_conversion_time[dev_data->res_index]; + + ret = ms_sensors_convert_and_read( + dev_data->client, + MS_SENSORS_TP_T_CONVERSION_START + + dev_data->res_index * 2, + MS_SENSORS_TP_ADC_READ, + delay, &t_adc); + if (ret) { + mutex_unlock(&dev_data->lock); + return ret; + } + + ret = ms_sensors_convert_and_read( + dev_data->client, + MS_SENSORS_TP_P_CONVERSION_START + + dev_data->res_index * 2, + MS_SENSORS_TP_ADC_READ, + delay, &p_adc); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + dt = (s32)t_adc - (prom[5] << 8); + + /* Actual temperature = 2000 + dT * TEMPSENS */ + temp = 2000 + (((s64)dt * prom[6]) >> 23); + + /* Second order temperature compensation */ + if (temp < 2000) { + s64 tmp = (s64)temp - 2000; + + t2 = (3 * ((s64)dt * (s64)dt)) >> 33; + off2 = (61 * tmp * tmp) >> 4; + sens2 = (29 * tmp * tmp) >> 4; + + if (temp < -1500) { + s64 tmp = (s64)temp + 1500; + + off2 += 17 * tmp * tmp; + sens2 += 9 * tmp * tmp; + } + } else { + t2 = (5 * ((s64)dt * (s64)dt)) >> 38; + off2 = 0; + sens2 = 0; + } + + /* OFF = OFF_T1 + TCO * dT */ + off = (((s64)prom[2]) << 17) + ((((s64)prom[4]) * (s64)dt) >> 6); + off -= off2; + + /* Sensitivity at actual temperature = SENS_T1 + TCS * dT */ + sens = (((s64)prom[1]) << 16) + (((s64)prom[3] * dt) >> 7); + sens -= sens2; + + /* Temperature compensated pressure = D1 * SENS - OFF */ + *temperature = (temp - t2) * 10; + *pressure = (u32)(((((s64)p_adc * sens) >> 21) - off) >> 15); + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_read_temp_and_pressure, "IIO_MEAS_SPEC_SENSORS"); + +MODULE_DESCRIPTION("Measurement-Specialties common i2c driver"); +MODULE_AUTHOR("William Markezana "); +MODULE_AUTHOR("Ludovic Tancerel "); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/iio/common/ms_sensors/ms_sensors_i2c.h b/drivers/iio/common/ms_sensors/ms_sensors_i2c.h new file mode 100644 index 000000000..f15b973f2 --- /dev/null +++ b/drivers/iio/common/ms_sensors/ms_sensors_i2c.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Measurements Specialties common sensor driver + * + * Copyright (c) 2015 Measurement-Specialties + */ + +#ifndef _MS_SENSORS_I2C_H +#define _MS_SENSORS_I2C_H + +#include +#include + +#define MS_SENSORS_TP_PROM_WORDS_NB 8 + +/** + * struct ms_ht_dev - Humidity/Temperature sensor device structure + * @client: i2c client + * @lock: lock protecting the i2c conversion + * @res_index: index to selected sensor resolution + */ +struct ms_ht_dev { + struct i2c_client *client; + struct mutex lock; + u8 res_index; +}; + +/** + * struct ms_hw_data - Temperature/Pressure sensor hardware data + * @prom_len: number of words in the PROM + * @max_res_index: maximum sensor resolution index + */ +struct ms_tp_hw_data { + u8 prom_len; + u8 max_res_index; +}; + +/** + * struct ms_tp_dev - Temperature/Pressure sensor device structure + * @client: i2c client + * @lock: lock protecting the i2c conversion + * @prom: array of PROM coefficients used for conversion. Added element + * for CRC computation + * @res_index: index to selected sensor resolution + */ +struct ms_tp_dev { + struct i2c_client *client; + struct mutex lock; + const struct ms_tp_hw_data *hw; + u16 prom[MS_SENSORS_TP_PROM_WORDS_NB]; + u8 res_index; +}; + +int ms_sensors_reset(void *cli, u8 cmd, unsigned int delay); +int ms_sensors_read_prom_word(void *cli, int cmd, u16 *word); +int ms_sensors_convert_and_read(void *cli, u8 conv, u8 rd, + unsigned int delay, u32 *adc); +int ms_sensors_read_serial(struct i2c_client *client, u64 *sn); +ssize_t ms_sensors_show_serial(struct ms_ht_dev *dev_data, char *buf); +ssize_t ms_sensors_write_resolution(struct ms_ht_dev *dev_data, u8 i); +ssize_t ms_sensors_show_battery_low(struct ms_ht_dev *dev_data, char *buf); +ssize_t ms_sensors_show_heater(struct ms_ht_dev *dev_data, char *buf); +ssize_t ms_sensors_write_heater(struct ms_ht_dev *dev_data, + const char *buf, size_t len); +int ms_sensors_ht_read_temperature(struct ms_ht_dev *dev_data, + s32 *temperature); +int ms_sensors_ht_read_humidity(struct ms_ht_dev *dev_data, + u32 *humidity); +int ms_sensors_tp_read_prom(struct ms_tp_dev *dev_data); +int ms_sensors_read_temp_and_pressure(struct ms_tp_dev *dev_data, + int *temperature, + unsigned int *pressure); + +#endif /* _MS_SENSORS_I2C_H */ diff --git a/drivers/iio/common/scmi_sensors/Kconfig b/drivers/iio/common/scmi_sensors/Kconfig new file mode 100644 index 000000000..67e084cbb --- /dev/null +++ b/drivers/iio/common/scmi_sensors/Kconfig @@ -0,0 +1,18 @@ +# +# IIO over SCMI +# +# When adding new entries keep the list in alphabetical order + +menu "IIO SCMI Sensors" + +config IIO_SCMI + tristate "IIO SCMI" + depends on ARM_SCMI_PROTOCOL + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for IIO SCMI Driver. + This provides ARM SCMI Protocol based IIO device. + This driver provides support for accelerometer and gyroscope + sensors available on SCMI based platforms. +endmenu diff --git a/drivers/iio/common/scmi_sensors/Makefile b/drivers/iio/common/scmi_sensors/Makefile new file mode 100644 index 000000000..645e0fce1 --- /dev/null +++ b/drivers/iio/common/scmi_sensors/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the IIO over SCMI +# +obj-$(CONFIG_IIO_SCMI) += scmi_iio.o diff --git a/drivers/iio/common/scmi_sensors/scmi_iio.c b/drivers/iio/common/scmi_sensors/scmi_iio.c new file mode 100644 index 000000000..442b40ef2 --- /dev/null +++ b/drivers/iio/common/scmi_sensors/scmi_iio.c @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * System Control and Management Interface(SCMI) based IIO sensor driver + * + * Copyright (C) 2021 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCMI_IIO_NUM_OF_AXIS 3 + +struct scmi_iio_priv { + const struct scmi_sensor_proto_ops *sensor_ops; + struct scmi_protocol_handle *ph; + const struct scmi_sensor_info *sensor_info; + struct iio_dev *indio_dev; + /* lock to protect against multiple access to the device */ + struct mutex lock; + /* adding one additional channel for timestamp */ + s64 iio_buf[SCMI_IIO_NUM_OF_AXIS + 1]; + struct notifier_block sensor_update_nb; + u32 *freq_avail; +}; + +static int scmi_iio_sensor_update_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct scmi_sensor_update_report *sensor_update = data; + struct iio_dev *scmi_iio_dev; + struct scmi_iio_priv *sensor; + s8 tstamp_scale; + u64 time, time_ns; + int i; + + if (sensor_update->readings_count == 0) + return NOTIFY_DONE; + + sensor = container_of(nb, struct scmi_iio_priv, sensor_update_nb); + + for (i = 0; i < sensor_update->readings_count; i++) + sensor->iio_buf[i] = sensor_update->readings[i].value; + + if (!sensor->sensor_info->timestamped) { + time_ns = ktime_to_ns(sensor_update->timestamp); + } else { + /* + * All the axes are supposed to have the same value for timestamp. + * We are just using the values from the Axis 0 here. + */ + time = sensor_update->readings[0].timestamp; + + /* + * Timestamp returned by SCMI is in seconds and is equal to + * time * power-of-10 multiplier(tstamp_scale) seconds. + * Converting the timestamp to nanoseconds (10⁹) below. + */ + tstamp_scale = sensor->sensor_info->tstamp_scale + 9; + if (tstamp_scale < 0) { + do_div(time, int_pow(10, abs(tstamp_scale))); + time_ns = time; + } else { + time_ns = time * int_pow(10, tstamp_scale); + } + } + + scmi_iio_dev = sensor->indio_dev; + iio_push_to_buffers_with_timestamp(scmi_iio_dev, sensor->iio_buf, + time_ns); + return NOTIFY_OK; +} + +static int scmi_iio_buffer_preenable(struct iio_dev *iio_dev) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config = 0; + int err; + + if (sensor->sensor_info->timestamped) + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_TSTAMP_ENABLED_MASK, + SCMI_SENS_CFG_TSTAMP_ENABLE); + + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_ENABLE); + err = sensor->sensor_ops->config_set(sensor->ph, + sensor->sensor_info->id, + sensor_config); + if (err) + dev_err(&iio_dev->dev, "Error in enabling sensor %s err %d", + sensor->sensor_info->name, err); + + return err; +} + +static int scmi_iio_buffer_postdisable(struct iio_dev *iio_dev) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config = 0; + int err; + + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_DISABLE); + err = sensor->sensor_ops->config_set(sensor->ph, + sensor->sensor_info->id, + sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in disabling sensor %s with err %d", + sensor->sensor_info->name, err); + } + + return err; +} + +static const struct iio_buffer_setup_ops scmi_iio_buffer_ops = { + .preenable = scmi_iio_buffer_preenable, + .postdisable = scmi_iio_buffer_postdisable, +}; + +static int scmi_iio_set_odr_val(struct iio_dev *iio_dev, int val, int val2) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u64 sec, mult, uHz, sf; + u32 sensor_config; + char buf[32]; + + int err = sensor->sensor_ops->config_get(sensor->ph, + sensor->sensor_info->id, + &sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in getting sensor config for sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + uHz = val * MICROHZ_PER_HZ + val2; + + /* + * The seconds field in the sensor interval in SCMI is 16 bits long + * Therefore seconds = 1/Hz <= 0xFFFF. As floating point calculations are + * discouraged in the kernel driver code, to calculate the scale factor (sf) + * (1* 1000000 * sf)/uHz <= 0xFFFF. Therefore, sf <= (uHz * 0xFFFF)/1000000 + * To calculate the multiplier,we convert the sf into char string and + * count the number of characters + */ + sf = uHz * 0xFFFF; + do_div(sf, MICROHZ_PER_HZ); + mult = scnprintf(buf, sizeof(buf), "%llu", sf) - 1; + + sec = int_pow(10, mult) * MICROHZ_PER_HZ; + do_div(sec, uHz); + if (sec == 0) { + dev_err(&iio_dev->dev, + "Trying to set invalid sensor update value for sensor %s", + sensor->sensor_info->name); + return -EINVAL; + } + + sensor_config &= ~SCMI_SENS_CFG_UPDATE_SECS_MASK; + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_UPDATE_SECS_MASK, sec); + sensor_config &= ~SCMI_SENS_CFG_UPDATE_EXP_MASK; + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_UPDATE_EXP_MASK, -mult); + + if (sensor->sensor_info->timestamped) { + sensor_config &= ~SCMI_SENS_CFG_TSTAMP_ENABLED_MASK; + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_TSTAMP_ENABLED_MASK, + SCMI_SENS_CFG_TSTAMP_ENABLE); + } + + sensor_config &= ~SCMI_SENS_CFG_ROUND_MASK; + sensor_config |= + FIELD_PREP(SCMI_SENS_CFG_ROUND_MASK, SCMI_SENS_CFG_ROUND_AUTO); + + err = sensor->sensor_ops->config_set(sensor->ph, + sensor->sensor_info->id, + sensor_config); + if (err) + dev_err(&iio_dev->dev, + "Error in setting sensor update interval for sensor %s value %u err %d", + sensor->sensor_info->name, sensor_config, err); + + return err; +} + +static int scmi_iio_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + int err; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&sensor->lock); + err = scmi_iio_set_odr_val(iio_dev, val, val2); + mutex_unlock(&sensor->lock); + return err; + default: + return -EINVAL; + } +} + +static int scmi_iio_read_avail(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = sensor->freq_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = sensor->sensor_info->intervals.count * 2; + if (sensor->sensor_info->intervals.segmented) + return IIO_AVAIL_RANGE; + else + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static void convert_ns_to_freq(u64 interval_ns, u64 *hz, u64 *uhz) +{ + u64 rem, freq; + + freq = NSEC_PER_SEC; + rem = do_div(freq, interval_ns); + *hz = freq; + *uhz = rem * 1000000UL; + do_div(*uhz, interval_ns); +} + +static int scmi_iio_get_odr_val(struct iio_dev *iio_dev, int *val, int *val2) +{ + u64 sensor_update_interval, sensor_interval_mult, hz, uhz; + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config; + int mult; + + int err = sensor->sensor_ops->config_get(sensor->ph, + sensor->sensor_info->id, + &sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in getting sensor config for sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + sensor_update_interval = + SCMI_SENS_CFG_GET_UPDATE_SECS(sensor_config) * NSEC_PER_SEC; + + mult = SCMI_SENS_CFG_GET_UPDATE_EXP(sensor_config); + if (mult < 0) { + sensor_interval_mult = int_pow(10, abs(mult)); + do_div(sensor_update_interval, sensor_interval_mult); + } else { + sensor_interval_mult = int_pow(10, mult); + sensor_update_interval = + sensor_update_interval * sensor_interval_mult; + } + + convert_ns_to_freq(sensor_update_interval, &hz, &uhz); + *val = hz; + *val2 = uhz; + return 0; +} + +static int scmi_iio_read_channel_data(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, int *val, int *val2) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config; + struct scmi_sensor_reading readings[SCMI_IIO_NUM_OF_AXIS]; + int err; + + sensor_config = FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_ENABLE); + err = sensor->sensor_ops->config_set( + sensor->ph, sensor->sensor_info->id, sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in enabling sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + err = sensor->sensor_ops->reading_get_timestamped( + sensor->ph, sensor->sensor_info->id, + sensor->sensor_info->num_axis, readings); + if (err) { + dev_err(&iio_dev->dev, + "Error in reading raw attribute for sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + sensor_config = FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_DISABLE); + err = sensor->sensor_ops->config_set( + sensor->ph, sensor->sensor_info->id, sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in disabling sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + *val = lower_32_bits(readings[ch->scan_index].value); + *val2 = upper_32_bits(readings[ch->scan_index].value); + + return IIO_VAL_INT_64; +} + +static int scmi_iio_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + s8 scale; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + scale = sensor->sensor_info->axis[ch->scan_index].scale; + if (scale < 0) { + *val = 1; + *val2 = int_pow(10, abs(scale)); + return IIO_VAL_FRACTIONAL; + } + *val = int_pow(10, scale); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = scmi_iio_get_odr_val(iio_dev, val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = scmi_iio_read_channel_data(iio_dev, ch, val, val2); + iio_device_release_direct(iio_dev); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info scmi_iio_info = { + .read_raw = scmi_iio_read_raw, + .read_avail = scmi_iio_read_avail, + .write_raw = scmi_iio_write_raw, +}; + +static ssize_t scmi_iio_get_raw_available(struct iio_dev *iio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u64 resolution, rem; + s64 min_range, max_range; + s8 exponent, scale; + int len = 0; + + /* + * All the axes are supposed to have the same value for range and resolution. + * We are just using the values from the Axis 0 here. + */ + if (sensor->sensor_info->axis[0].extended_attrs) { + min_range = sensor->sensor_info->axis[0].attrs.min_range; + max_range = sensor->sensor_info->axis[0].attrs.max_range; + resolution = sensor->sensor_info->axis[0].resolution; + exponent = sensor->sensor_info->axis[0].exponent; + scale = sensor->sensor_info->axis[0].scale; + + /* + * To provide the raw value for the resolution to the userspace, + * need to divide the resolution exponent by the sensor scale + */ + exponent = exponent - scale; + if (exponent < 0) { + rem = do_div(resolution, + int_pow(10, abs(exponent)) + ); + len = sysfs_emit(buf, + "[%lld %llu.%llu %lld]\n", min_range, + resolution, rem, max_range); + } else { + resolution = resolution * int_pow(10, exponent); + len = sysfs_emit(buf, "[%lld %llu %lld]\n", + min_range, resolution, max_range); + } + } + return len; +} + +static const struct iio_chan_spec_ext_info scmi_iio_ext_info[] = { + { + .name = "raw_available", + .read = scmi_iio_get_raw_available, + .shared = IIO_SHARED_BY_TYPE, + }, + { } +}; + +static void scmi_iio_set_data_channel(struct iio_chan_spec *iio_chan, + enum iio_chan_type type, + enum iio_modifier mod, int scan_index) +{ + iio_chan->type = type; + iio_chan->modified = 1; + iio_chan->channel2 = mod; + iio_chan->info_mask_separate = + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_RAW); + iio_chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ); + iio_chan->info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + iio_chan->scan_index = scan_index; + iio_chan->scan_type.sign = 's'; + iio_chan->scan_type.realbits = 64; + iio_chan->scan_type.storagebits = 64; + iio_chan->scan_type.endianness = IIO_LE; + iio_chan->ext_info = scmi_iio_ext_info; +} + +static int scmi_iio_get_chan_modifier(const char *name, + enum iio_modifier *modifier) +{ + char *pch, mod; + + if (!name) + return -EINVAL; + + pch = strrchr(name, '_'); + if (!pch) + return -EINVAL; + + mod = *(pch + 1); + switch (mod) { + case 'X': + *modifier = IIO_MOD_X; + return 0; + case 'Y': + *modifier = IIO_MOD_Y; + return 0; + case 'Z': + *modifier = IIO_MOD_Z; + return 0; + default: + return -EINVAL; + } +} + +static int scmi_iio_get_chan_type(u8 scmi_type, enum iio_chan_type *iio_type) +{ + switch (scmi_type) { + case METERS_SEC_SQUARED: + *iio_type = IIO_ACCEL; + return 0; + case RADIANS_SEC: + *iio_type = IIO_ANGL_VEL; + return 0; + default: + return -EINVAL; + } +} + +static u64 scmi_iio_convert_interval_to_ns(u32 val) +{ + u64 sensor_update_interval = + SCMI_SENS_INTVL_GET_SECS(val) * NSEC_PER_SEC; + u64 sensor_interval_mult; + int mult; + + mult = SCMI_SENS_INTVL_GET_EXP(val); + if (mult < 0) { + sensor_interval_mult = int_pow(10, abs(mult)); + do_div(sensor_update_interval, sensor_interval_mult); + } else { + sensor_interval_mult = int_pow(10, mult); + sensor_update_interval = + sensor_update_interval * sensor_interval_mult; + } + return sensor_update_interval; +} + +static int scmi_iio_set_sampling_freq_avail(struct iio_dev *iio_dev) +{ + u64 cur_interval_ns, low_interval_ns, high_interval_ns, step_size_ns, + hz, uhz; + unsigned int cur_interval, low_interval, high_interval, step_size; + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + int i; + + sensor->freq_avail = + devm_kcalloc(&iio_dev->dev, + array_size(sensor->sensor_info->intervals.count, 2), + sizeof(*sensor->freq_avail), + GFP_KERNEL); + if (!sensor->freq_avail) + return -ENOMEM; + + if (sensor->sensor_info->intervals.segmented) { + low_interval = sensor->sensor_info->intervals + .desc[SCMI_SENS_INTVL_SEGMENT_LOW]; + low_interval_ns = scmi_iio_convert_interval_to_ns(low_interval); + convert_ns_to_freq(low_interval_ns, &hz, &uhz); + sensor->freq_avail[0] = hz; + sensor->freq_avail[1] = uhz; + + step_size = sensor->sensor_info->intervals + .desc[SCMI_SENS_INTVL_SEGMENT_STEP]; + step_size_ns = scmi_iio_convert_interval_to_ns(step_size); + convert_ns_to_freq(step_size_ns, &hz, &uhz); + sensor->freq_avail[2] = hz; + sensor->freq_avail[3] = uhz; + + high_interval = sensor->sensor_info->intervals + .desc[SCMI_SENS_INTVL_SEGMENT_HIGH]; + high_interval_ns = + scmi_iio_convert_interval_to_ns(high_interval); + convert_ns_to_freq(high_interval_ns, &hz, &uhz); + sensor->freq_avail[4] = hz; + sensor->freq_avail[5] = uhz; + } else { + for (i = 0; i < sensor->sensor_info->intervals.count; i++) { + cur_interval = sensor->sensor_info->intervals.desc[i]; + cur_interval_ns = + scmi_iio_convert_interval_to_ns(cur_interval); + convert_ns_to_freq(cur_interval_ns, &hz, &uhz); + sensor->freq_avail[i * 2] = hz; + sensor->freq_avail[i * 2 + 1] = uhz; + } + } + return 0; +} + +static struct iio_dev * +scmi_alloc_iiodev(struct scmi_device *sdev, + const struct scmi_sensor_proto_ops *ops, + struct scmi_protocol_handle *ph, + const struct scmi_sensor_info *sensor_info) +{ + struct iio_chan_spec *iio_channels; + struct scmi_iio_priv *sensor; + enum iio_modifier modifier; + enum iio_chan_type type; + struct iio_dev *iiodev; + struct device *dev = &sdev->dev; + const struct scmi_handle *handle = sdev->handle; + int i, ret; + + iiodev = devm_iio_device_alloc(dev, sizeof(*sensor)); + if (!iiodev) + return ERR_PTR(-ENOMEM); + + iiodev->modes = INDIO_DIRECT_MODE; + sensor = iio_priv(iiodev); + sensor->sensor_ops = ops; + sensor->ph = ph; + sensor->sensor_info = sensor_info; + sensor->sensor_update_nb.notifier_call = scmi_iio_sensor_update_cb; + sensor->indio_dev = iiodev; + mutex_init(&sensor->lock); + + /* adding one additional channel for timestamp */ + iiodev->num_channels = sensor_info->num_axis + 1; + iiodev->name = sensor_info->name; + iiodev->info = &scmi_iio_info; + + iio_channels = + devm_kcalloc(dev, iiodev->num_channels, + sizeof(*iio_channels), + GFP_KERNEL); + if (!iio_channels) + return ERR_PTR(-ENOMEM); + + ret = scmi_iio_set_sampling_freq_avail(iiodev); + if (ret < 0) + return ERR_PTR(ret); + + for (i = 0; i < sensor_info->num_axis; i++) { + ret = scmi_iio_get_chan_type(sensor_info->axis[i].type, &type); + if (ret < 0) + return ERR_PTR(ret); + + ret = scmi_iio_get_chan_modifier(sensor_info->axis[i].name, + &modifier); + if (ret < 0) + return ERR_PTR(ret); + + scmi_iio_set_data_channel(&iio_channels[i], type, modifier, + sensor_info->axis[i].id); + } + + ret = handle->notify_ops->devm_event_notifier_register(sdev, + SCMI_PROTOCOL_SENSOR, SCMI_EVENT_SENSOR_UPDATE, + &sensor->sensor_info->id, + &sensor->sensor_update_nb); + if (ret) + return dev_err_ptr_probe(&iiodev->dev, ret, + "Error in registering sensor update notifier for sensor %s\n", + sensor->sensor_info->name); + + iio_channels[i] = IIO_CHAN_SOFT_TIMESTAMP(i); + iiodev->channels = iio_channels; + return iiodev; +} + +static int scmi_iio_dev_probe(struct scmi_device *sdev) +{ + const struct scmi_sensor_info *sensor_info; + struct scmi_handle *handle = sdev->handle; + const struct scmi_sensor_proto_ops *sensor_ops; + struct scmi_protocol_handle *ph; + struct device *dev = &sdev->dev; + struct iio_dev *scmi_iio_dev; + u16 nr_sensors; + int err = -ENODEV, i; + + if (!handle) + return -ENODEV; + + sensor_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_SENSOR, &ph); + if (IS_ERR(sensor_ops)) + return dev_err_probe(dev, PTR_ERR(sensor_ops), + "SCMI device has no sensor interface\n"); + + nr_sensors = sensor_ops->count_get(ph); + if (!nr_sensors) { + dev_dbg(dev, "0 sensors found via SCMI bus\n"); + return -ENODEV; + } + + for (i = 0; i < nr_sensors; i++) { + sensor_info = sensor_ops->info_get(ph, i); + if (!sensor_info) { + return dev_err_probe(dev, -EINVAL, + "SCMI sensor %d has missing info\n", i); + } + + /* This driver only supports 3-axis accel and gyro, skipping other sensors */ + if (sensor_info->num_axis != SCMI_IIO_NUM_OF_AXIS) + continue; + + /* This driver only supports 3-axis accel and gyro, skipping other sensors */ + if (sensor_info->axis[0].type != METERS_SEC_SQUARED && + sensor_info->axis[0].type != RADIANS_SEC) + continue; + + scmi_iio_dev = scmi_alloc_iiodev(sdev, sensor_ops, ph, + sensor_info); + if (IS_ERR(scmi_iio_dev)) { + return dev_err_probe(dev, PTR_ERR(scmi_iio_dev), + "failed to allocate IIO device for sensor %s\n", + sensor_info->name); + } + + err = devm_iio_kfifo_buffer_setup(&scmi_iio_dev->dev, + scmi_iio_dev, + &scmi_iio_buffer_ops); + if (err < 0) { + return dev_err_probe(dev, err, + "IIO buffer setup error at sensor %s\n", + sensor_info->name); + } + + err = devm_iio_device_register(dev, scmi_iio_dev); + if (err) + return dev_err_probe(dev, err, + "IIO device registration failed at sensor %s\n", + sensor_info->name); + } + return err; +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_SENSOR, "iiodev" }, + { } +}; + +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_iiodev_driver = { + .name = "scmi-sensor-iiodev", + .probe = scmi_iio_dev_probe, + .id_table = scmi_id_table, +}; + +module_scmi_driver(scmi_iiodev_driver); + +MODULE_AUTHOR("Jyoti Bhayana "); +MODULE_DESCRIPTION("SCMI IIO Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/ssp_sensors/Kconfig b/drivers/iio/common/ssp_sensors/Kconfig new file mode 100644 index 000000000..5262409e4 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# SSP sensor drivers and commons configuration +# +menu "SSP Sensor Common" + +config IIO_SSP_SENSORS_COMMONS + tristate "Commons for all SSP Sensor IIO drivers" + depends on IIO_SSP_SENSORHUB + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build commons for SSP sensors. + To compile this as a module, choose M here: the module + will be called ssp_iio. + +config IIO_SSP_SENSORHUB + tristate "Samsung Sensorhub driver" + depends on SPI + select MFD_CORE + help + SSP driver for sensorhub. + If you say yes here you get ssp support for sensorhub. + To compile this driver as a module, choose M here: the + module will be called sensorhub. + +endmenu diff --git a/drivers/iio/common/ssp_sensors/Makefile b/drivers/iio/common/ssp_sensors/Makefile new file mode 100644 index 000000000..ba831429b --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for SSP sensor drivers and commons. +# + +sensorhub-objs := ssp_dev.o ssp_spi.o +obj-$(CONFIG_IIO_SSP_SENSORHUB) += sensorhub.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_iio.o diff --git a/drivers/iio/common/ssp_sensors/ssp.h b/drivers/iio/common/ssp_sensors/ssp.h new file mode 100644 index 000000000..f649cdecc --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#ifndef __SSP_SENSORHUB_H__ +#define __SSP_SENSORHUB_H__ + +#include +#include +#include +#include +#include + +#define SSP_DEVICE_ID 0x55 + +#ifdef SSP_DBG +#define ssp_dbg(format, ...) pr_info("[SSP] "format, ##__VA_ARGS__) +#else +#define ssp_dbg(format, ...) +#endif + +#define SSP_SW_RESET_TIME 3000 +/* Sensor polling in ms */ +#define SSP_DEFAULT_POLLING_DELAY 200 +#define SSP_DEFAULT_RETRIES 3 +#define SSP_DATA_PACKET_SIZE 960 +#define SSP_HEADER_BUFFER_SIZE 4 + +enum { + SSP_KERNEL_BINARY = 0, + SSP_KERNEL_CRASHED_BINARY, +}; + +enum { + SSP_INITIALIZATION_STATE = 0, + SSP_NO_SENSOR_STATE, + SSP_ADD_SENSOR_STATE, + SSP_RUNNING_SENSOR_STATE, +}; + +/* Firmware download STATE */ +enum { + SSP_FW_DL_STATE_FAIL = -1, + SSP_FW_DL_STATE_NONE = 0, + SSP_FW_DL_STATE_NEED_TO_SCHEDULE, + SSP_FW_DL_STATE_SCHEDULED, + SSP_FW_DL_STATE_DOWNLOADING, + SSP_FW_DL_STATE_SYNC, + SSP_FW_DL_STATE_DONE, +}; + +#define SSP_INVALID_REVISION 99999 +#define SSP_INVALID_REVISION2 0xffffff + +/* AP -> SSP Instruction */ +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD 0xa1 +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_RM 0xa2 +#define SSP_MSG2SSP_INST_REMOVE_ALL 0xa3 +#define SSP_MSG2SSP_INST_CHANGE_DELAY 0xa4 +#define SSP_MSG2SSP_INST_LIBRARY_ADD 0xb1 +#define SSP_MSG2SSP_INST_LIBRARY_REMOVE 0xb2 +#define SSP_MSG2SSP_INST_LIB_NOTI 0xb4 +#define SSP_MSG2SSP_INST_LIB_DATA 0xc1 + +#define SSP_MSG2SSP_AP_MCU_SET_GYRO_CAL 0xcd +#define SSP_MSG2SSP_AP_MCU_SET_ACCEL_CAL 0xce +#define SSP_MSG2SSP_AP_STATUS_SHUTDOWN 0xd0 +#define SSP_MSG2SSP_AP_STATUS_WAKEUP 0xd1 +#define SSP_MSG2SSP_AP_STATUS_SLEEP 0xd2 +#define SSP_MSG2SSP_AP_STATUS_RESUME 0xd3 +#define SSP_MSG2SSP_AP_STATUS_SUSPEND 0xd4 +#define SSP_MSG2SSP_AP_STATUS_RESET 0xd5 +#define SSP_MSG2SSP_AP_STATUS_POW_CONNECTED 0xd6 +#define SSP_MSG2SSP_AP_STATUS_POW_DISCONNECTED 0xd7 +#define SSP_MSG2SSP_AP_TEMPHUMIDITY_CAL_DONE 0xda +#define SSP_MSG2SSP_AP_MCU_SET_DUMPMODE 0xdb +#define SSP_MSG2SSP_AP_MCU_DUMP_CHECK 0xdc +#define SSP_MSG2SSP_AP_MCU_BATCH_FLUSH 0xdd +#define SSP_MSG2SSP_AP_MCU_BATCH_COUNT 0xdf + +#define SSP_MSG2SSP_AP_WHOAMI 0x0f +#define SSP_MSG2SSP_AP_FIRMWARE_REV 0xf0 +#define SSP_MSG2SSP_AP_SENSOR_FORMATION 0xf1 +#define SSP_MSG2SSP_AP_SENSOR_PROXTHRESHOLD 0xf2 +#define SSP_MSG2SSP_AP_SENSOR_BARCODE_EMUL 0xf3 +#define SSP_MSG2SSP_AP_SENSOR_SCANNING 0xf4 +#define SSP_MSG2SSP_AP_SET_MAGNETIC_HWOFFSET 0xf5 +#define SSP_MSG2SSP_AP_GET_MAGNETIC_HWOFFSET 0xf6 +#define SSP_MSG2SSP_AP_SENSOR_GESTURE_CURRENT 0xf7 +#define SSP_MSG2SSP_AP_GET_THERM 0xf8 +#define SSP_MSG2SSP_AP_GET_BIG_DATA 0xf9 +#define SSP_MSG2SSP_AP_SET_BIG_DATA 0xfa +#define SSP_MSG2SSP_AP_START_BIG_DATA 0xfb +#define SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX 0xfd +#define SSP_MSG2SSP_AP_SENSOR_TILT 0xea +#define SSP_MSG2SSP_AP_MCU_SET_TIME 0xfe +#define SSP_MSG2SSP_AP_MCU_GET_TIME 0xff + +#define SSP_MSG2SSP_AP_FUSEROM 0x01 + +/* voice data */ +#define SSP_TYPE_WAKE_UP_VOICE_SERVICE 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_AM 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_GRAMMER 0x02 + +/* Factory Test */ +#define SSP_ACCELEROMETER_FACTORY 0x80 +#define SSP_GYROSCOPE_FACTORY 0x81 +#define SSP_GEOMAGNETIC_FACTORY 0x82 +#define SSP_PRESSURE_FACTORY 0x85 +#define SSP_GESTURE_FACTORY 0x86 +#define SSP_TEMPHUMIDITY_CRC_FACTORY 0x88 +#define SSP_GYROSCOPE_TEMP_FACTORY 0x8a +#define SSP_GYROSCOPE_DPS_FACTORY 0x8b +#define SSP_MCU_FACTORY 0x8c +#define SSP_MCU_SLEEP_FACTORY 0x8d + +/* SSP -> AP ACK about write CMD */ +#define SSP_MSG_ACK 0x80 /* ACK from SSP to AP */ +#define SSP_MSG_NAK 0x70 /* NAK from SSP to AP */ + +struct ssp_sensorhub_info { + char *fw_name; + char *fw_crashed_name; + unsigned int fw_rev; + const u8 * const mag_table; + const unsigned int mag_length; +}; + +/* ssp_msg options bit */ +#define SSP_RW 0 +#define SSP_INDEX 3 + +#define SSP_AP2HUB_READ 0 +#define SSP_AP2HUB_WRITE 1 +#define SSP_HUB2AP_WRITE 2 +#define SSP_AP2HUB_READY 3 +#define SSP_AP2HUB_RETURN 4 + +/** + * struct ssp_data - ssp platformdata structure + * @spi: spi device + * @sensorhub_info: info about sensorhub board specific features + * @wdt_timer: watchdog timer + * @work_wdt: watchdog work + * @work_firmware: firmware upgrade work queue + * @work_refresh: refresh work queue for reset request from MCU + * @shut_down: shut down flag + * @mcu_dump_mode: mcu dump mode for debug + * @time_syncing: time syncing indication flag + * @timestamp: previous time in ns calculated for time syncing + * @check_status: status table for each sensor + * @com_fail_cnt: communication fail count + * @reset_cnt: reset count + * @timeout_cnt: timeout count + * @available_sensors: available sensors seen by sensorhub (bit array) + * @cur_firm_rev: cached current firmware revision + * @last_resume_state: last AP resume/suspend state used to handle the PM + * state of ssp + * @last_ap_state: (obsolete) sleep notification for MCU + * @sensor_enable: sensor enable mask + * @delay_buf: data acquisition intervals table + * @batch_latency_buf: yet unknown but existing in communication protocol + * @batch_opt_buf: yet unknown but existing in communication protocol + * @accel_position: yet unknown but existing in communication protocol + * @mag_position: yet unknown but existing in communication protocol + * @fw_dl_state: firmware download state + * @comm_lock: lock protecting the handshake + * @pending_lock: lock protecting pending list and completion + * @mcu_reset_gpiod: mcu reset line + * @ap_mcu_gpiod: ap to mcu gpio line + * @mcu_ap_gpiod: mcu to ap gpio line + * @pending_list: pending list for messages queued to be sent/read + * @sensor_devs: registered IIO devices table + * @enable_refcount: enable reference count for wdt (watchdog timer) + * @header_buffer: cache aligned buffer for packet header + */ +struct ssp_data { + struct spi_device *spi; + const struct ssp_sensorhub_info *sensorhub_info; + struct timer_list wdt_timer; + struct work_struct work_wdt; + struct delayed_work work_refresh; + + bool shut_down; + bool mcu_dump_mode; + bool time_syncing; + int64_t timestamp; + + int check_status[SSP_SENSOR_MAX]; + + unsigned int com_fail_cnt; + unsigned int reset_cnt; + unsigned int timeout_cnt; + + unsigned int available_sensors; + unsigned int cur_firm_rev; + + char last_resume_state; + char last_ap_state; + + unsigned int sensor_enable; + u32 delay_buf[SSP_SENSOR_MAX]; + s32 batch_latency_buf[SSP_SENSOR_MAX]; + s8 batch_opt_buf[SSP_SENSOR_MAX]; + + int accel_position; + int mag_position; + int fw_dl_state; + + struct mutex comm_lock; + struct mutex pending_lock; + + struct gpio_desc *mcu_reset_gpiod; + struct gpio_desc *ap_mcu_gpiod; + struct gpio_desc *mcu_ap_gpiod; + + struct list_head pending_list; + + struct iio_dev *sensor_devs[SSP_SENSOR_MAX]; + atomic_t enable_refcount; + + __le16 header_buffer[SSP_HEADER_BUFFER_SIZE / sizeof(__le16)] __aligned(IIO_DMA_MINALIGN); +}; + +void ssp_clean_pending_list(struct ssp_data *data); + +int ssp_command(struct ssp_data *data, char command, int arg); + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length); + +int ssp_irq_msg(struct ssp_data *data); + +int ssp_get_chipid(struct ssp_data *data); + +int ssp_set_magnetic_matrix(struct ssp_data *data); + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data); + +unsigned int ssp_get_firmware_rev(struct ssp_data *data); + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay); + +#endif /* __SSP_SENSORHUB_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_dev.c b/drivers/iio/common/ssp_sensors/ssp_dev.c new file mode 100644 index 000000000..828fcfe1d --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_dev.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include "ssp.h" + +#define SSP_WDT_TIME 10000 +#define SSP_LIMIT_RESET_CNT 20 +#define SSP_LIMIT_TIMEOUT_CNT 3 + +/* It is possible that it is max clk rate for version 1.0 of bootcode */ +#define SSP_BOOT_SPI_HZ 400000 + +/* + * These fields can look enigmatic but this structure is used mainly to flat + * some values and depends on command type. + */ +struct ssp_instruction { + __le32 a; + __le32 b; + u8 c; +} __packed__; + +static const u8 ssp_magnitude_table[] = {110, 85, 171, 71, 203, 195, 0, 67, + 208, 56, 175, 244, 206, 213, 0, 92, 250, 0, 55, 48, 189, 252, 171, + 243, 13, 45, 250}; + +static const struct ssp_sensorhub_info ssp_rinato_info = { + .fw_name = "ssp_B2.fw", + .fw_crashed_name = "ssp_crashed.fw", + .fw_rev = 14052300, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct ssp_sensorhub_info ssp_thermostat_info = { + .fw_name = "thermostat_B2.fw", + .fw_crashed_name = "ssp_crashed.fw", + .fw_rev = 14080600, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct mfd_cell sensorhub_sensor_devs[] = { + { + .name = "ssp-accelerometer", + }, + { + .name = "ssp-gyroscope", + }, +}; + +static void ssp_toggle_mcu_reset_gpio(struct ssp_data *data) +{ + gpiod_set_value(data->mcu_reset_gpiod, 0); + usleep_range(1000, 1200); + gpiod_set_value(data->mcu_reset_gpiod, 1); + msleep(50); +} + +static void ssp_sync_available_sensors(struct ssp_data *data) +{ + int i, ret; + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + if (data->available_sensors & BIT(i)) { + ret = ssp_enable_sensor(data, i, data->delay_buf[i]); + if (ret < 0) { + dev_err(&data->spi->dev, + "Sync sensor nr: %d fail\n", i); + continue; + } + } + } + + ret = ssp_command(data, SSP_MSG2SSP_AP_MCU_SET_DUMPMODE, + data->mcu_dump_mode); + if (ret < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_MCU_SET_DUMPMODE failed\n"); +} + +static void ssp_enable_mcu(struct ssp_data *data, bool enable) +{ + dev_info(&data->spi->dev, "current shutdown = %d, old = %d\n", enable, + data->shut_down); + + if (enable && data->shut_down) { + data->shut_down = false; + enable_irq(data->spi->irq); + enable_irq_wake(data->spi->irq); + } else if (!enable && !data->shut_down) { + data->shut_down = true; + disable_irq(data->spi->irq); + disable_irq_wake(data->spi->irq); + } else { + dev_warn(&data->spi->dev, "current shutdown = %d, old = %d\n", + enable, data->shut_down); + } +} + +/* + * This function is the first one which communicates with the mcu so it is + * possible that the first attempt will fail + */ +static int ssp_check_fwbl(struct ssp_data *data) +{ + int retries = 0; + + while (retries++ < 5) { + data->cur_firm_rev = ssp_get_firmware_rev(data); + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_warn(&data->spi->dev, + "Invalid revision, trying %d time\n", retries); + } else { + break; + } + } + + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_err(&data->spi->dev, "SSP_INVALID_REVISION\n"); + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + } + + dev_info(&data->spi->dev, + "MCU Firm Rev : Old = %8u, New = %8u\n", + data->cur_firm_rev, + data->sensorhub_info->fw_rev); + + if (data->cur_firm_rev != data->sensorhub_info->fw_rev) + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + + return SSP_FW_DL_STATE_NONE; +} + +static void ssp_reset_mcu(struct ssp_data *data) +{ + ssp_enable_mcu(data, false); + ssp_clean_pending_list(data); + ssp_toggle_mcu_reset_gpio(data); + ssp_enable_mcu(data, true); +} + +static void ssp_wdt_work_func(struct work_struct *work) +{ + struct ssp_data *data = container_of(work, struct ssp_data, work_wdt); + + dev_err(&data->spi->dev, "%s - Sensor state: 0x%x, RC: %u, CC: %u\n", + __func__, data->available_sensors, data->reset_cnt, + data->com_fail_cnt); + + ssp_reset_mcu(data); + data->com_fail_cnt = 0; + data->timeout_cnt = 0; +} + +static void ssp_wdt_timer_func(struct timer_list *t) +{ + struct ssp_data *data = timer_container_of(data, t, wdt_timer); + + switch (data->fw_dl_state) { + case SSP_FW_DL_STATE_FAIL: + case SSP_FW_DL_STATE_DOWNLOADING: + case SSP_FW_DL_STATE_SYNC: + goto _mod; + } + + if (data->timeout_cnt > SSP_LIMIT_TIMEOUT_CNT || + data->com_fail_cnt > SSP_LIMIT_RESET_CNT) + queue_work(system_power_efficient_wq, &data->work_wdt); +_mod: + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_enable_wdt_timer(struct ssp_data *data) +{ + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_disable_wdt_timer(struct ssp_data *data) +{ + timer_delete_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); +} + +/** + * ssp_get_sensor_delay() - gets sensor data acquisition period + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns acquisition period in ms + */ +u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type type) +{ + return data->delay_buf[type]; +} +EXPORT_SYMBOL_NS(ssp_get_sensor_delay, "IIO_SSP_SENSORS"); + +/** + * ssp_enable_sensor() - enables data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + switch (data->check_status[type]) { + case SSP_INITIALIZATION_STATE: + /* do calibration step, now just enable */ + case SSP_ADD_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD, + type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Enabling sensor failed\n"); + data->check_status[type] = SSP_NO_SENSOR_STATE; + goto derror; + } + + data->sensor_enable |= BIT(type); + data->check_status[type] = SSP_RUNNING_SENSOR_STATE; + break; + case SSP_RUNNING_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, + "Changing sensor delay failed\n"); + goto derror; + } + break; + default: + data->check_status[type] = SSP_ADD_SENSOR_STATE; + break; + } + + data->delay_buf[type] = delay; + + if (atomic_inc_return(&data->enable_refcount) == 1) + ssp_enable_wdt_timer(data); + + return 0; + +derror: + return ret; +} +EXPORT_SYMBOL_NS(ssp_enable_sensor, "IIO_SSP_SENSORS"); + +/** + * ssp_change_delay() - changes data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + ret = ssp_send_instruction(data, SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Changing sensor delay failed\n"); + return ret; + } + + data->delay_buf[type] = delay; + + return 0; +} +EXPORT_SYMBOL_NS(ssp_change_delay, "IIO_SSP_SENSORS"); + +/** + * ssp_disable_sensor() - disables sensor + * + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns 0 or negative value in case of error + */ +int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type) +{ + int ret; + __le32 command; + + if (data->sensor_enable & BIT(type)) { + command = cpu_to_le32(data->delay_buf[type]); + + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_RM, + type, (u8 *)&command, + sizeof(command)); + if (ret < 0) { + dev_err(&data->spi->dev, "Remove sensor fail\n"); + return ret; + } + + data->sensor_enable &= ~BIT(type); + } + + data->check_status[type] = SSP_ADD_SENSOR_STATE; + + if (atomic_dec_and_test(&data->enable_refcount)) + ssp_disable_wdt_timer(data); + + return 0; +} +EXPORT_SYMBOL_NS(ssp_disable_sensor, "IIO_SSP_SENSORS"); + +static irqreturn_t ssp_irq_thread_fn(int irq, void *dev_id) +{ + struct ssp_data *data = dev_id; + + /* + * This wrapper is done to preserve error path for ssp_irq_msg, also + * it is defined in different file. + */ + ssp_irq_msg(data); + + return IRQ_HANDLED; +} + +static int ssp_initialize_mcu(struct ssp_data *data) +{ + int ret; + + ssp_clean_pending_list(data); + + ret = ssp_get_chipid(data); + if (ret != SSP_DEVICE_ID) { + dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n", __func__, + ret < 0 ? "is not working" : "identification failed", + ret); + return ret < 0 ? ret : -ENODEV; + } + + dev_info(&data->spi->dev, "MCU device ID = %d\n", ret); + + /* + * needs clarification, for now do not want to export all transfer + * methods to sensors' drivers + */ + ret = ssp_set_magnetic_matrix(data); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s - ssp_set_magnetic_matrix failed\n", __func__); + return ret; + } + + data->available_sensors = ssp_get_sensor_scanning_info(data); + if (data->available_sensors == 0) { + dev_err(&data->spi->dev, + "%s - ssp_get_sensor_scanning_info failed\n", __func__); + return -EIO; + } + + data->cur_firm_rev = ssp_get_firmware_rev(data); + dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n", + data->cur_firm_rev); + + return ssp_command(data, SSP_MSG2SSP_AP_MCU_DUMP_CHECK, 0); +} + +/* + * sensorhub can request its reinitialization as some brutal and rare error + * handling. It can be requested from the MCU. + */ +static void ssp_refresh_task(struct work_struct *work) +{ + struct ssp_data *data = container_of((struct delayed_work *)work, + struct ssp_data, work_refresh); + + dev_info(&data->spi->dev, "refreshing\n"); + + data->reset_cnt++; + + if (ssp_initialize_mcu(data) >= 0) { + ssp_sync_available_sensors(data); + if (data->last_ap_state != 0) + ssp_command(data, data->last_ap_state, 0); + + if (data->last_resume_state != 0) + ssp_command(data, data->last_resume_state, 0); + + data->timeout_cnt = 0; + data->com_fail_cnt = 0; + } +} + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay) +{ + cancel_delayed_work_sync(&data->work_refresh); + + return queue_delayed_work(system_power_efficient_wq, + &data->work_refresh, + msecs_to_jiffies(delay)); +} + +static const struct of_device_id ssp_of_match[] = { + { + .compatible = "samsung,sensorhub-rinato", + .data = &ssp_rinato_info, + }, { + .compatible = "samsung,sensorhub-thermostat", + .data = &ssp_thermostat_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, ssp_of_match); + +static struct ssp_data *ssp_parse_dt(struct device *dev) +{ + struct ssp_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->mcu_ap_gpiod = devm_gpiod_get(dev, "mcu-ap", GPIOD_IN); + if (IS_ERR(data->mcu_ap_gpiod)) + return NULL; + + data->ap_mcu_gpiod = devm_gpiod_get(dev, "ap-mcu", GPIOD_OUT_HIGH); + if (IS_ERR(data->ap_mcu_gpiod)) + return NULL; + + data->mcu_reset_gpiod = devm_gpiod_get(dev, "mcu-reset", + GPIOD_OUT_HIGH); + if (IS_ERR(data->mcu_reset_gpiod)) + return NULL; + + data->sensorhub_info = device_get_match_data(dev); + + dev_set_drvdata(dev, data); + + return data; +} + +/** + * ssp_register_consumer() - registers iio consumer in ssp framework + * + * @indio_dev: consumer iio device + * @type: ssp sensor type + */ +void ssp_register_consumer(struct iio_dev *indio_dev, enum ssp_sensor_type type) +{ + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + data->sensor_devs[type] = indio_dev; +} +EXPORT_SYMBOL_NS(ssp_register_consumer, "IIO_SSP_SENSORS"); + +static int ssp_probe(struct spi_device *spi) +{ + int ret, i; + struct ssp_data *data; + + data = ssp_parse_dt(&spi->dev); + if (!data) { + dev_err(&spi->dev, "Failed to find platform data\n"); + return -ENODEV; + } + + ret = mfd_add_devices(&spi->dev, PLATFORM_DEVID_NONE, + sensorhub_sensor_devs, + ARRAY_SIZE(sensorhub_sensor_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(&spi->dev, "mfd add devices fail\n"); + return ret; + } + + spi->mode = SPI_MODE_1; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "Failed to setup spi\n"); + goto err_setup_spi; + } + + data->fw_dl_state = SSP_FW_DL_STATE_NONE; + data->spi = spi; + spi_set_drvdata(spi, data); + + mutex_init(&data->comm_lock); + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + data->delay_buf[i] = SSP_DEFAULT_POLLING_DELAY; + data->batch_latency_buf[i] = 0; + data->batch_opt_buf[i] = 0; + data->check_status[i] = SSP_INITIALIZATION_STATE; + } + + data->delay_buf[SSP_BIO_HRM_LIB] = 100; + + data->time_syncing = true; + + mutex_init(&data->pending_lock); + INIT_LIST_HEAD(&data->pending_list); + + atomic_set(&data->enable_refcount, 0); + + INIT_WORK(&data->work_wdt, ssp_wdt_work_func); + INIT_DELAYED_WORK(&data->work_refresh, ssp_refresh_task); + + timer_setup(&data->wdt_timer, ssp_wdt_timer_func, 0); + + ret = request_threaded_irq(data->spi->irq, NULL, + ssp_irq_thread_fn, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "SSP_Int", data); + if (ret < 0) { + dev_err(&spi->dev, "Irq request fail\n"); + goto err_setup_irq; + } + + /* Let's start with enabled one so irq balance could be ok */ + data->shut_down = false; + + /* just to avoid unbalanced irq set wake up */ + enable_irq_wake(data->spi->irq); + + data->fw_dl_state = ssp_check_fwbl(data); + if (data->fw_dl_state == SSP_FW_DL_STATE_NONE) { + ret = ssp_initialize_mcu(data); + if (ret < 0) { + dev_err(&spi->dev, "Initialize_mcu failed\n"); + goto err_read_reg; + } + } else { + dev_err(&spi->dev, "Firmware version not supported\n"); + ret = -EPERM; + goto err_read_reg; + } + + return 0; + +err_read_reg: + free_irq(data->spi->irq, data); +err_setup_irq: + mutex_destroy(&data->pending_lock); + mutex_destroy(&data->comm_lock); +err_setup_spi: + mfd_remove_devices(&spi->dev); + + dev_err(&spi->dev, "Probe failed!\n"); + + return ret; +} + +static void ssp_remove(struct spi_device *spi) +{ + struct ssp_data *data = spi_get_drvdata(spi); + + if (ssp_command(data, SSP_MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_STATUS_SHUTDOWN failed\n"); + + ssp_enable_mcu(data, false); + ssp_disable_wdt_timer(data); + + ssp_clean_pending_list(data); + + free_irq(data->spi->irq, data); + cancel_delayed_work_sync(&data->work_refresh); + + timer_delete_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); + + mutex_destroy(&data->comm_lock); + mutex_destroy(&data->pending_lock); + + mfd_remove_devices(&spi->dev); +} + +static int ssp_suspend(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_SUSPEND; + + if (atomic_read(&data->enable_refcount) > 0) + ssp_disable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_SUSPEND, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_SUSPEND failed\n", __func__); + + ssp_enable_wdt_timer(data); + return ret; + } + + data->time_syncing = false; + disable_irq(data->spi->irq); + + return 0; +} + +static int ssp_resume(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + enable_irq(data->spi->irq); + + if (atomic_read(&data->enable_refcount) > 0) + ssp_enable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_RESUME, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_RESUME failed\n", __func__); + ssp_disable_wdt_timer(data); + return ret; + } + + /* timesyncing is set by MCU */ + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_RESUME; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ssp_pm_ops, ssp_suspend, ssp_resume); + +static struct spi_driver ssp_driver = { + .probe = ssp_probe, + .remove = ssp_remove, + .driver = { + .pm = pm_sleep_ptr(&ssp_pm_ops), + .of_match_table = ssp_of_match, + .name = "sensorhub" + }, +}; + +module_spi_driver(ssp_driver); + +MODULE_DESCRIPTION("ssp sensorhub driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio.c b/drivers/iio/common/ssp_sensors/ssp_iio.c new file mode 100644 index 000000000..78ac689de --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "ssp_iio_sensor.h" + +/** + * ssp_common_buffer_postenable() - generic postenable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + /* the allocation is made in post because scan size is known in this + * moment + * */ + spd->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL | GFP_DMA); + if (!spd->buffer) + return -ENOMEM; + + return ssp_enable_sensor(data, spd->type, + ssp_get_sensor_delay(data, spd->type)); +} +EXPORT_SYMBOL_NS(ssp_common_buffer_postenable, "IIO_SSP_SENSORS"); + +/** + * ssp_common_buffer_postdisable() - generic postdisable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev) +{ + int ret; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + ret = ssp_disable_sensor(data, spd->type); + if (ret < 0) + return ret; + + kfree(spd->buffer); + + return ret; +} +EXPORT_SYMBOL_NS(ssp_common_buffer_postdisable, "IIO_SSP_SENSORS"); + +/** + * ssp_common_process_data() - Common process data callback for ssp sensors + * + * @indio_dev: iio device + * @buf: source buffer + * @len: sensor data length + * @timestamp: system timestamp + * + * Returns 0 or negative value in case of error + */ +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp) +{ + int64_t calculated_time; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + + if (indio_dev->scan_bytes == 0) + return 0; + + /* + * it always sends full set of samples, remember about available masks + */ + memcpy(spd->buffer, buf, len); + + calculated_time = timestamp + + (int64_t)get_unaligned_le32(buf + len) * MEGA; + + return iio_push_to_buffers_with_timestamp(indio_dev, spd->buffer, + calculated_time); +} +EXPORT_SYMBOL_NS(ssp_common_process_data, "IIO_SSP_SENSORS"); + +MODULE_AUTHOR("Karol Wrona "); +MODULE_DESCRIPTION("Samsung sensorhub commons"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_SSP_SENSORS"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h new file mode 100644 index 000000000..05fcad61c --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SSP_IIO_SENSOR_H__ +#define __SSP_IIO_SENSOR_H__ + +#define SSP_CHANNEL_AG(_type, _mod, _index) \ +{ \ + .type = _type,\ + .modified = 1,\ + .channel2 = _mod,\ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .scan_index = _index,\ + .scan_type = {\ + .sign = 's',\ + .realbits = 16,\ + .storagebits = 16,\ + .shift = 0,\ + .endianness = IIO_LE,\ + },\ +} + +#define SSP_MS_PER_S 1000 +#define SSP_INVERTED_SCALING_FACTOR 1000000U + +#define SSP_FACTOR_WITH_MS \ + (SSP_INVERTED_SCALING_FACTOR * SSP_MS_PER_S) + +int ssp_common_buffer_postenable(struct iio_dev *indio_dev); + +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev); + +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp); + +/* Converts time in ms to frequency */ +static inline void ssp_convert_to_freq(u32 time, int *integer_part, + int *fractional) +{ + if (time == 0) { + *fractional = 0; + *integer_part = 0; + return; + } + + *integer_part = SSP_FACTOR_WITH_MS / time; + *fractional = *integer_part % SSP_INVERTED_SCALING_FACTOR; + *integer_part = *integer_part / SSP_INVERTED_SCALING_FACTOR; +} + +/* Converts frequency to time in ms */ +static inline int ssp_convert_to_time(int integer_part, int fractional) +{ + u64 value; + + value = (u64)integer_part * SSP_INVERTED_SCALING_FACTOR + fractional; + if (value == 0) + return 0; + + return div64_u64((u64)SSP_FACTOR_WITH_MS, value); +} +#endif /* __SSP_IIO_SENSOR_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_spi.c b/drivers/iio/common/ssp_sensors/ssp_spi.c new file mode 100644 index 000000000..08ed92859 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_spi.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include "ssp.h" + +#define SSP_DEV (&data->spi->dev) +#define SSP_GET_MESSAGE_TYPE(data) ((data) & (3 << SSP_RW)) + +/* + * SSP -> AP Instruction + * They tell what packet type can be expected. In the future there will + * be less of them. BYPASS means common sensor packets with accel, gyro, + * hrm etc. data. LIBRARY and META are mock-up's for now. + */ +#define SSP_MSG2AP_INST_BYPASS_DATA 0x37 +#define SSP_MSG2AP_INST_LIBRARY_DATA 0x01 +#define SSP_MSG2AP_INST_DEBUG_DATA 0x03 +#define SSP_MSG2AP_INST_BIG_DATA 0x04 +#define SSP_MSG2AP_INST_META_DATA 0x05 +#define SSP_MSG2AP_INST_TIME_SYNC 0x06 +#define SSP_MSG2AP_INST_RESET 0x07 + +#define SSP_UNIMPLEMENTED -1 + +struct ssp_msg_header { + u8 cmd; + __le16 length; + __le16 options; + __le32 data; +} __packed; + +struct ssp_msg { + u16 length; + u16 options; + struct list_head list; + struct completion *done; + struct ssp_msg_header *h; + char *buffer; +}; + +static const int ssp_offset_map[SSP_SENSOR_MAX] = { + [SSP_ACCELEROMETER_SENSOR] = SSP_ACCELEROMETER_SIZE + + SSP_TIME_SIZE, + [SSP_GYROSCOPE_SENSOR] = SSP_GYROSCOPE_SIZE + + SSP_TIME_SIZE, + [SSP_GEOMAGNETIC_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_RAW] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PRESSURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GESTURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_TEMPERATURE_HUMIDITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_LIGHT_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_RAW] = SSP_UNIMPLEMENTED, + [SSP_ORIENTATION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_DETECTOR] = SSP_UNIMPLEMENTED, + [SSP_SIG_MOTION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GYRO_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GAME_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_COUNTER] = SSP_UNIMPLEMENTED, + [SSP_BIO_HRM_RAW] = SSP_BIO_HRM_RAW_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_RAW_FAC] = SSP_BIO_HRM_RAW_FAC_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_LIB] = SSP_BIO_HRM_LIB_SIZE + + SSP_TIME_SIZE, +}; + +#define SSP_HEADER_SIZE (sizeof(struct ssp_msg_header)) +#define SSP_HEADER_SIZE_ALIGNED (ALIGN(SSP_HEADER_SIZE, 4)) + +static struct ssp_msg *ssp_create_msg(u8 cmd, u16 len, u16 opt, u32 data) +{ + struct ssp_msg_header h; + struct ssp_msg *msg; + + msg = kzalloc_obj(*msg); + if (!msg) + return NULL; + + h.cmd = cmd; + h.length = cpu_to_le16(len); + h.options = cpu_to_le16(opt); + h.data = cpu_to_le32(data); + + msg->buffer = kzalloc(SSP_HEADER_SIZE_ALIGNED + len, + GFP_KERNEL | GFP_DMA); + if (!msg->buffer) { + kfree(msg); + return NULL; + } + + msg->length = len; + msg->options = opt; + + memcpy(msg->buffer, &h, SSP_HEADER_SIZE); + + return msg; +} + +/* + * It is a bit heavy to do it this way but often the function is used to compose + * the message from smaller chunks which are placed on the stack. Often the + * chunks are small so memcpy should be optimized. + */ +static inline void ssp_fill_buffer(struct ssp_msg *m, unsigned int offset, + const void *src, unsigned int len) +{ + memcpy(&m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], src, len); +} + +static inline void ssp_get_buffer(struct ssp_msg *m, unsigned int offset, + void *dest, unsigned int len) +{ + memcpy(dest, &m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], len); +} + +#define SSP_GET_BUFFER_AT_INDEX(m, index) \ + ((m)->buffer[SSP_HEADER_SIZE_ALIGNED + (index)]) +#define SSP_SET_BUFFER_AT_INDEX(m, index, val) \ + ((m)->buffer[SSP_HEADER_SIZE_ALIGNED + (index)] = val) + +static void ssp_clean_msg(struct ssp_msg *m) +{ + kfree(m->buffer); + kfree(m); +} + +static int ssp_print_mcu_debug(char *data_frame, int *data_index, + int received_len) +{ + int length = data_frame[(*data_index)++]; + + if (length > received_len - *data_index || length <= 0) { + ssp_dbg("[SSP]: MSG From MCU-invalid debug length(%d/%d)\n", + length, received_len); + return -EPROTO; + } + + ssp_dbg("[SSP]: MSG From MCU - %s\n", &data_frame[*data_index]); + + *data_index += length; + + return 0; +} + +/* + * It was designed that way - additional lines to some kind of handshake, + * please do not ask why - only the firmware guy can know it. + */ +static int ssp_check_lines(struct ssp_data *data, bool state) +{ + int delay_cnt = 0; + + gpiod_set_value_cansleep(data->ap_mcu_gpiod, state); + + while (gpiod_get_value_cansleep(data->mcu_ap_gpiod) != state) { + usleep_range(3000, 3500); + + if (data->shut_down || delay_cnt++ > 500) { + dev_err(SSP_DEV, "%s:timeout, hw ack wait fail %d\n", + __func__, state); + + if (!state) + gpiod_set_value_cansleep(data->ap_mcu_gpiod, 1); + + return -ETIMEDOUT; + } + } + + return 0; +} + +static int ssp_do_transfer(struct ssp_data *data, struct ssp_msg *msg, + struct completion *done, int timeout) +{ + int status; + /* + * check if this is a short one way message or the whole transfer has + * second part after an interrupt + */ + const bool use_no_irq = msg->length == 0; + + if (data->shut_down) + return -EPERM; + + msg->done = done; + + mutex_lock(&data->comm_lock); + + status = ssp_check_lines(data, false); + if (status < 0) + goto _error_locked; + + status = spi_write(data->spi, msg->buffer, SSP_HEADER_SIZE); + if (status < 0) { + gpiod_set_value_cansleep(data->ap_mcu_gpiod, 1); + dev_err(SSP_DEV, "%s spi_write fail\n", __func__); + goto _error_locked; + } + + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_add_tail(&msg->list, &data->pending_list); + mutex_unlock(&data->pending_lock); + } + + status = ssp_check_lines(data, true); + if (status < 0) { + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + } + goto _error_locked; + } + + mutex_unlock(&data->comm_lock); + + if (!use_no_irq && done) + if (wait_for_completion_timeout(done, + msecs_to_jiffies(timeout)) == + 0) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + + data->timeout_cnt++; + return -ETIMEDOUT; + } + + return 0; + +_error_locked: + mutex_unlock(&data->comm_lock); + data->timeout_cnt++; + return status; +} + +static inline int ssp_spi_sync_command(struct ssp_data *data, + struct ssp_msg *msg) +{ + return ssp_do_transfer(data, msg, NULL, 0); +} + +static int ssp_spi_sync(struct ssp_data *data, struct ssp_msg *msg, + int timeout) +{ + DECLARE_COMPLETION_ONSTACK(done); + + if (WARN_ON(!msg->length)) + return -EPERM; + + return ssp_do_transfer(data, msg, &done, timeout); +} + +static int ssp_handle_big_data(struct ssp_data *data, char *dataframe, int *idx) +{ + /* mock-up, it will be changed with adding another sensor types */ + *idx += 8; + return 0; +} + +static int ssp_parse_dataframe(struct ssp_data *data, char *dataframe, int len) +{ + int idx, sd; + struct ssp_sensor_data *spd; + struct iio_dev **indio_devs = data->sensor_devs; + + for (idx = 0; idx < len;) { + switch (dataframe[idx++]) { + case SSP_MSG2AP_INST_BYPASS_DATA: + if (idx >= len) + return -EPROTO; + sd = dataframe[idx++]; + if (sd < 0 || sd >= SSP_SENSOR_MAX) { + dev_err(SSP_DEV, + "Mcu data frame1 error %d\n", sd); + return -EPROTO; + } + + if (indio_devs[sd]) { + spd = iio_priv(indio_devs[sd]); + if (spd->process_data) { + if (idx >= len) + return -EPROTO; + spd->process_data(indio_devs[sd], + &dataframe[idx], + data->timestamp); + } + } else { + dev_err(SSP_DEV, "no client for frame\n"); + } + + idx += ssp_offset_map[sd]; + break; + case SSP_MSG2AP_INST_DEBUG_DATA: + if (idx >= len) + return -EPROTO; + sd = ssp_print_mcu_debug(dataframe, &idx, len); + if (sd) { + dev_err(SSP_DEV, + "Mcu data frame3 error %d\n", sd); + return sd; + } + break; + case SSP_MSG2AP_INST_LIBRARY_DATA: + idx += len; + break; + case SSP_MSG2AP_INST_BIG_DATA: + ssp_handle_big_data(data, dataframe, &idx); + break; + case SSP_MSG2AP_INST_TIME_SYNC: + data->time_syncing = true; + break; + case SSP_MSG2AP_INST_RESET: + ssp_queue_ssp_refresh_task(data, 0); + break; + } + } + + if (data->time_syncing) + data->timestamp = ktime_get_real_ns(); + + return 0; +} + +/* threaded irq */ +int ssp_irq_msg(struct ssp_data *data) +{ + char *buffer; + u8 msg_type; + int ret; + u16 length, msg_options; + struct ssp_msg *msg = NULL, *iter, *n; + + ret = spi_read(data->spi, data->header_buffer, SSP_HEADER_BUFFER_SIZE); + if (ret < 0) { + dev_err(SSP_DEV, "header read fail\n"); + return ret; + } + + length = le16_to_cpu(data->header_buffer[1]); + msg_options = le16_to_cpu(data->header_buffer[0]); + + if (length == 0) { + dev_err(SSP_DEV, "length received from mcu is 0\n"); + return -EINVAL; + } + + msg_type = SSP_GET_MESSAGE_TYPE(msg_options); + + switch (msg_type) { + case SSP_AP2HUB_READ: + case SSP_AP2HUB_WRITE: + /* + * this is a small list, a few elements - the packets can be + * received with no order + */ + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(iter, n, &data->pending_list, list) { + if (iter->options == msg_options) { + list_del(&iter->list); + msg = iter; + break; + } + } + + if (!msg) { + /* + * here can be implemented dead messages handling + * but the slave should not send such ones - it is to + * check but let's handle this + */ + buffer = kmalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) { + ret = -ENOMEM; + goto _unlock; + } + + /* got dead packet so it is always an error */ + ret = spi_read(data->spi, buffer, length); + if (ret >= 0) + ret = -EPROTO; + + kfree(buffer); + + dev_err(SSP_DEV, "No match error %x\n", + msg_options); + + goto _unlock; + } + + if (msg_type == SSP_AP2HUB_READ) + ret = spi_read(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + + if (msg_type == SSP_AP2HUB_WRITE) { + ret = spi_write(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + if (msg_options & SSP_AP2HUB_RETURN) { + msg->options = + SSP_AP2HUB_READ | SSP_AP2HUB_RETURN; + msg->length = 1; + + list_add_tail(&msg->list, &data->pending_list); + goto _unlock; + } + } + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); +_unlock: + mutex_unlock(&data->pending_lock); + break; + case SSP_HUB2AP_WRITE: + buffer = kzalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) + return -ENOMEM; + + ret = spi_read(data->spi, buffer, length); + if (ret < 0) { + dev_err(SSP_DEV, "spi read fail\n"); + kfree(buffer); + break; + } + + ret = ssp_parse_dataframe(data, buffer, length); + + kfree(buffer); + break; + + default: + dev_err(SSP_DEV, "unknown msg type\n"); + return -EPROTO; + } + + return ret; +} + +void ssp_clean_pending_list(struct ssp_data *data) +{ + struct ssp_msg *msg, *n; + + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(msg, n, &data->pending_list, list) { + list_del(&msg->list); + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); + } + mutex_unlock(&data->pending_lock); +} + +int ssp_command(struct ssp_data *data, char command, int arg) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(command, 0, SSP_AP2HUB_WRITE, arg); + if (!msg) + return -ENOMEM; + + ssp_dbg("%s - command 0x%x %d\n", __func__, command, arg); + + ret = ssp_spi_sync_command(data, msg); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length) +{ + int ret; + struct ssp_msg *msg; + + if (data->fw_dl_state == SSP_FW_DL_STATE_DOWNLOADING) { + dev_err(SSP_DEV, "%s - Skip Inst! DL state = %d\n", + __func__, data->fw_dl_state); + return -EBUSY; + } else if (!(data->available_sensors & BIT(sensor_type)) && + (inst <= SSP_MSG2SSP_INST_CHANGE_DELAY)) { + dev_err(SSP_DEV, "%s - Bypass Inst Skip! - %u\n", + __func__, sensor_type); + return -EIO; /* just fail */ + } + + msg = ssp_create_msg(inst, length + 2, SSP_AP2HUB_WRITE, 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, &sensor_type, 1); + ssp_fill_buffer(msg, 1, send_buf, length); + + ssp_dbg("%s - Inst = 0x%x, Sensor Type = 0x%x, data = %u\n", + __func__, inst, sensor_type, send_buf[1]); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_get_chipid(struct ssp_data *data) +{ + int ret; + char buffer; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_WHOAMI, 1, SSP_AP2HUB_READ, 0); + if (!msg) + return -ENOMEM; + + ret = ssp_spi_sync(data, msg, 1000); + + buffer = SSP_GET_BUFFER_AT_INDEX(msg, 0); + + ssp_clean_msg(msg); + + return ret < 0 ? ret : buffer; +} + +int ssp_set_magnetic_matrix(struct ssp_data *data) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX, + data->sensorhub_info->mag_length, SSP_AP2HUB_WRITE, + 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, data->sensorhub_info->mag_table, + data->sensorhub_info->mag_length); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data) +{ + int ret; + __le32 result; + u32 cpu_result = 0; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_SENSOR_SCANNING, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return 0; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - spi read fail %d\n", __func__, ret); + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + cpu_result = le32_to_cpu(result); + + dev_info(SSP_DEV, "%s state: 0x%08x\n", __func__, cpu_result); + +_exit: + ssp_clean_msg(msg); + return cpu_result; +} + +unsigned int ssp_get_firmware_rev(struct ssp_data *data) +{ + int ret; + __le32 result; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_FIRMWARE_REV, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return SSP_INVALID_REVISION; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - transfer fail %d\n", __func__, ret); + ret = SSP_INVALID_REVISION; + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + ret = le32_to_cpu(result); + +_exit: + ssp_clean_msg(msg); + return ret; +} diff --git a/drivers/iio/common/st_sensors/Kconfig b/drivers/iio/common/st_sensors/Kconfig new file mode 100644 index 000000000..eda8f347f --- /dev/null +++ b/drivers/iio/common/st_sensors/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# STMicroelectronics sensors common library +# + +config IIO_ST_SENSORS_I2C + tristate + select REGMAP_I2C + +config IIO_ST_SENSORS_SPI + tristate + select REGMAP_SPI + +config IIO_ST_SENSORS_CORE + tristate diff --git a/drivers/iio/common/st_sensors/Makefile b/drivers/iio/common/st_sensors/Makefile new file mode 100644 index 000000000..f7fb3b79b --- /dev/null +++ b/drivers/iio/common/st_sensors/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the STMicroelectronics sensor common modules. +# + +obj-$(CONFIG_IIO_ST_SENSORS_I2C) += st_sensors_i2c.o +obj-$(CONFIG_IIO_ST_SENSORS_SPI) += st_sensors_spi.o +obj-$(CONFIG_IIO_ST_SENSORS_CORE) += st_sensors.o +st_sensors-y := st_sensors_core.o +st_sensors-$(CONFIG_IIO_BUFFER) += st_sensors_buffer.o +st_sensors-$(CONFIG_IIO_TRIGGER) += st_sensors_trigger.o diff --git a/drivers/iio/common/st_sensors/st_sensors_buffer.c b/drivers/iio/common/st_sensors/st_sensors_buffer.c new file mode 100644 index 000000000..57f087c22 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_buffer.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors buffer library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +static int st_sensors_get_buffer_element(struct iio_dev *indio_dev, u8 *buf) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int num_data_channels = sdata->num_data_channels; + int i; + + for_each_set_bit(i, indio_dev->active_scan_mask, num_data_channels) { + const struct iio_chan_spec *channel = &indio_dev->channels[i]; + unsigned int bytes_to_read = + DIV_ROUND_UP(channel->scan_type.realbits + + channel->scan_type.shift, 8); + unsigned int storage_bytes = + channel->scan_type.storagebits >> 3; + + buf = PTR_ALIGN(buf, storage_bytes); + if (regmap_bulk_read(sdata->regmap, channel->address, + buf, bytes_to_read) < 0) + return -EIO; + + /* Advance the buffer pointer */ + buf += storage_bytes; + } + + return 0; +} + +irqreturn_t st_sensors_trigger_handler(int irq, void *p) +{ + int len; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct st_sensor_data *sdata = iio_priv(indio_dev); + s64 timestamp; + + /* + * If we do timestamping here, do it before reading the values, because + * once we've read the values, new interrupts can occur (when using + * the hardware trigger) and the hw_timestamp may get updated. + * By storing it in a local variable first, we are safe. + */ + if (iio_trigger_using_own(indio_dev)) + timestamp = sdata->hw_timestamp; + else + timestamp = iio_get_time_ns(indio_dev); + + len = st_sensors_get_buffer_element(indio_dev, sdata->buffer_data); + if (len < 0) + goto st_sensors_get_buffer_element_error; + + iio_push_to_buffers_with_timestamp(indio_dev, sdata->buffer_data, + timestamp); + +st_sensors_get_buffer_element_error: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_NS(st_sensors_trigger_handler, "IIO_ST_SENSORS"); diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c new file mode 100644 index 000000000..76f91696f --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors core library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st_sensors_core.h" + +int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return regmap_update_bits(sdata->regmap, + reg_addr, mask, data << __ffs(mask)); +} + +int st_sensors_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + int err; + + if (!readval) + return regmap_write(sdata->regmap, reg, writeval); + + err = regmap_read(sdata->regmap, reg, readval); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_debugfs_reg_access, "IIO_ST_SENSORS"); + +static int st_sensors_match_odr(struct st_sensor_settings *sensor_settings, + unsigned int odr, struct st_sensor_odr_avl *odr_out) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sensor_settings->odr.odr_avl[i].hz == 0) + goto st_sensors_match_odr_error; + + if (sensor_settings->odr.odr_avl[i].hz == odr) { + odr_out->hz = sensor_settings->odr.odr_avl[i].hz; + odr_out->value = sensor_settings->odr.odr_avl[i].value; + ret = 0; + break; + } + } + +st_sensors_match_odr_error: + return ret; +} + +int st_sensors_set_odr(struct iio_dev *indio_dev, unsigned int odr) +{ + int err = 0; + struct st_sensor_odr_avl odr_out = {0, 0}; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&sdata->odr_lock); + + if (!sdata->sensor_settings->odr.mask) + goto unlock_mutex; + + err = st_sensors_match_odr(sdata->sensor_settings, odr, &odr_out); + if (err < 0) + goto unlock_mutex; + + if ((sdata->sensor_settings->odr.addr == + sdata->sensor_settings->pw.addr) && + (sdata->sensor_settings->odr.mask == + sdata->sensor_settings->pw.mask)) { + if (sdata->enabled == true) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->odr.addr, + sdata->sensor_settings->odr.mask, + odr_out.value); + } else { + err = 0; + } + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->odr.addr, + sdata->sensor_settings->odr.mask, + odr_out.value); + } + if (err >= 0) + sdata->odr = odr_out.hz; + +unlock_mutex: + mutex_unlock(&sdata->odr_lock); + + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_odr, "IIO_ST_SENSORS"); + +static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings, + unsigned int fs, int *index_fs_avl) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sensor_settings->fs.fs_avl[i].num == 0) + return ret; + + if (sensor_settings->fs.fs_avl[i].num == fs) { + *index_fs_avl = i; + ret = 0; + break; + } + } + + return ret; +} + +static int st_sensors_set_fullscale(struct iio_dev *indio_dev, unsigned int fs) +{ + int err, i = 0; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (sdata->sensor_settings->fs.addr == 0) + return 0; + + err = st_sensors_match_fs(sdata->sensor_settings, fs, &i); + if (err < 0) + goto st_accel_set_fullscale_error; + + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->fs.addr, + sdata->sensor_settings->fs.mask, + sdata->sensor_settings->fs.fs_avl[i].value); + if (err < 0) + goto st_accel_set_fullscale_error; + + sdata->current_fullscale = &sdata->sensor_settings->fs.fs_avl[i]; + return err; + +st_accel_set_fullscale_error: + dev_err(indio_dev->dev.parent, "failed to set new fullscale.\n"); + return err; +} + +int st_sensors_set_enable(struct iio_dev *indio_dev, bool enable) +{ + u8 tmp_value; + int err = -EINVAL; + bool found = false; + struct st_sensor_odr_avl odr_out = {0, 0}; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (enable) { + tmp_value = sdata->sensor_settings->pw.value_on; + if ((sdata->sensor_settings->odr.addr == + sdata->sensor_settings->pw.addr) && + (sdata->sensor_settings->odr.mask == + sdata->sensor_settings->pw.mask)) { + err = st_sensors_match_odr(sdata->sensor_settings, + sdata->odr, &odr_out); + if (err < 0) + goto set_enable_error; + tmp_value = odr_out.value; + found = true; + } + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->pw.addr, + sdata->sensor_settings->pw.mask, tmp_value); + if (err < 0) + goto set_enable_error; + + sdata->enabled = true; + + if (found) + sdata->odr = odr_out.hz; + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->pw.addr, + sdata->sensor_settings->pw.mask, + sdata->sensor_settings->pw.value_off); + if (err < 0) + goto set_enable_error; + + sdata->enabled = false; + } + +set_enable_error: + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_enable, "IIO_ST_SENSORS"); + +int st_sensors_set_axis_enable(struct iio_dev *indio_dev, u8 axis_enable) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + int err = 0; + + if (sdata->sensor_settings->enable_axis.addr) + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->enable_axis.addr, + sdata->sensor_settings->enable_axis.mask, + axis_enable); + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_axis_enable, "IIO_ST_SENSORS"); + + +int st_sensors_power_enable(struct iio_dev *indio_dev) +{ + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct device *parent = indio_dev->dev.parent; + int err; + + /* Regulators not mandatory, but if requested we should enable them. */ + err = devm_regulator_bulk_get_enable(parent, + ARRAY_SIZE(regulator_names), + regulator_names); + if (err) + return dev_err_probe(parent, err, "unable to enable supplies\n"); + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_power_enable, "IIO_ST_SENSORS"); + +static int st_sensors_set_drdy_int_pin(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) +{ + struct device *parent = indio_dev->dev.parent; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* Sensor does not support interrupts */ + if (!sdata->sensor_settings->drdy_irq.int1.addr && + !sdata->sensor_settings->drdy_irq.int2.addr) { + if (pdata->drdy_int_pin) + dev_info(parent, + "DRDY on pin INT%d specified, but sensor does not support interrupts\n", + pdata->drdy_int_pin); + return 0; + } + + switch (pdata->drdy_int_pin) { + case 1: + if (!sdata->sensor_settings->drdy_irq.int1.mask) { + dev_err(parent, "DRDY on INT1 not available.\n"); + return -EINVAL; + } + sdata->drdy_int_pin = 1; + break; + case 2: + if (!sdata->sensor_settings->drdy_irq.int2.mask) { + dev_err(parent, "DRDY on INT2 not available.\n"); + return -EINVAL; + } + sdata->drdy_int_pin = 2; + break; + default: + dev_err(parent, "DRDY on pdata not valid.\n"); + return -EINVAL; + } + + if (pdata->open_drain) { + if (!sdata->sensor_settings->drdy_irq.int1.addr_od && + !sdata->sensor_settings->drdy_irq.int2.addr_od) + dev_err(parent, + "open drain requested but unsupported.\n"); + else + sdata->int_pin_open_drain = true; + } + + return 0; +} + +static struct st_sensors_platform_data *st_sensors_dev_probe(struct device *dev, + struct st_sensors_platform_data *defdata) +{ + struct st_sensors_platform_data *pdata; + u32 val; + + if (!dev_fwnode(dev)) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + if (!device_property_read_u32(dev, "st,drdy-int-pin", &val) && (val <= 2)) + pdata->drdy_int_pin = (u8) val; + else + pdata->drdy_int_pin = defdata ? defdata->drdy_int_pin : 0; + + pdata->open_drain = device_property_read_bool(dev, "drive-open-drain"); + + return pdata; +} + +/** + * st_sensors_dev_name_probe() - device probe for ST sensor name + * @dev: driver model representation of the device. + * @name: device name buffer reference. + * @len: device name buffer length. + * + * In effect this function matches an ID to an internal kernel + * name for a certain sensor device, so that the rest of the autodetection can + * rely on that name from this point on. I2C/SPI devices will be renamed + * to match the internal kernel convention. + */ +void st_sensors_dev_name_probe(struct device *dev, char *name, int len) +{ + const void *match; + + match = device_get_match_data(dev); + if (!match) + return; + + /* The name from the match takes precedence if present */ + strscpy(name, match, len); +} +EXPORT_SYMBOL_NS(st_sensors_dev_name_probe, "IIO_ST_SENSORS"); + +int st_sensors_init_sensor(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) +{ + struct device *parent = indio_dev->dev.parent; + struct st_sensor_data *sdata = iio_priv(indio_dev); + struct st_sensors_platform_data *of_pdata; + int err = 0; + + mutex_init(&sdata->odr_lock); + + /* If OF/DT pdata exists, it will take precedence of anything else */ + of_pdata = st_sensors_dev_probe(parent, pdata); + if (IS_ERR(of_pdata)) + return PTR_ERR(of_pdata); + if (of_pdata) + pdata = of_pdata; + + if (pdata) { + err = st_sensors_set_drdy_int_pin(indio_dev, pdata); + if (err < 0) + return err; + } + + err = st_sensors_set_enable(indio_dev, false); + if (err < 0) + return err; + + /* Disable DRDY, this might be still be enabled after reboot. */ + err = st_sensors_set_dataready_irq(indio_dev, false); + if (err < 0) + return err; + + if (sdata->current_fullscale) { + err = st_sensors_set_fullscale(indio_dev, + sdata->current_fullscale->num); + if (err < 0) + return err; + } else + dev_info(parent, "Full-scale not possible\n"); + + err = st_sensors_set_odr(indio_dev, sdata->odr); + if (err < 0) + return err; + + /* set BDU */ + if (sdata->sensor_settings->bdu.addr) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->bdu.addr, + sdata->sensor_settings->bdu.mask, true); + if (err < 0) + return err; + } + + /* set DAS */ + if (sdata->sensor_settings->das.addr) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->das.addr, + sdata->sensor_settings->das.mask, 1); + if (err < 0) + return err; + } + + if (sdata->int_pin_open_drain) { + u8 addr, mask; + + if (sdata->drdy_int_pin == 1) { + addr = sdata->sensor_settings->drdy_irq.int1.addr_od; + mask = sdata->sensor_settings->drdy_irq.int1.mask_od; + } else { + addr = sdata->sensor_settings->drdy_irq.int2.addr_od; + mask = sdata->sensor_settings->drdy_irq.int2.mask_od; + } + + dev_info(parent, + "set interrupt line to open drain mode on pin %d\n", + sdata->drdy_int_pin); + err = st_sensors_write_data_with_mask(indio_dev, addr, + mask, 1); + if (err < 0) + return err; + } + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + + return err; +} +EXPORT_SYMBOL_NS(st_sensors_init_sensor, "IIO_ST_SENSORS"); + +int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) +{ + int err; + u8 drdy_addr, drdy_mask; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (!sdata->sensor_settings->drdy_irq.int1.addr && + !sdata->sensor_settings->drdy_irq.int2.addr) { + /* + * there are some devices (e.g. LIS3MDL) where drdy line is + * routed to a given pin and it is not possible to select a + * different one. Take into account irq status register + * to understand if irq trigger can be properly supported + */ + if (sdata->sensor_settings->drdy_irq.stat_drdy.addr) + sdata->hw_irq_trigger = enable; + return 0; + } + + /* Enable/Disable the interrupt generator 1. */ + if (sdata->sensor_settings->drdy_irq.ig1.en_addr > 0) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.ig1.en_addr, + sdata->sensor_settings->drdy_irq.ig1.en_mask, + (int)enable); + if (err < 0) + goto st_accel_set_dataready_irq_error; + } + + if (sdata->drdy_int_pin == 1) { + drdy_addr = sdata->sensor_settings->drdy_irq.int1.addr; + drdy_mask = sdata->sensor_settings->drdy_irq.int1.mask; + } else { + drdy_addr = sdata->sensor_settings->drdy_irq.int2.addr; + drdy_mask = sdata->sensor_settings->drdy_irq.int2.mask; + } + + /* Flag to the poll function that the hardware trigger is in use */ + sdata->hw_irq_trigger = enable; + + /* Enable/Disable the interrupt generator for data ready. */ + err = st_sensors_write_data_with_mask(indio_dev, drdy_addr, + drdy_mask, (int)enable); + +st_accel_set_dataready_irq_error: + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_dataready_irq, "IIO_ST_SENSORS"); + +int st_sensors_set_fullscale_by_gain(struct iio_dev *indio_dev, int scale) +{ + int err = -EINVAL, i; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if ((sdata->sensor_settings->fs.fs_avl[i].gain == scale) && + (sdata->sensor_settings->fs.fs_avl[i].gain != 0)) { + err = 0; + break; + } + } + if (err < 0) + goto st_sensors_match_scale_error; + + err = st_sensors_set_fullscale(indio_dev, + sdata->sensor_settings->fs.fs_avl[i].num); + +st_sensors_match_scale_error: + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_fullscale_by_gain, "IIO_ST_SENSORS"); + +static int st_sensors_read_axis_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *data) +{ + int err; + u8 *outdata; + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int byte_for_channel; + u32 tmp; + + byte_for_channel = DIV_ROUND_UP(ch->scan_type.realbits + + ch->scan_type.shift, 8); + outdata = sdata->buffer_data; + + err = regmap_bulk_read(sdata->regmap, ch->address, + outdata, byte_for_channel); + if (err < 0) + return err; + + if (byte_for_channel == 1) { + tmp = *outdata; + } else if (byte_for_channel == 2) { + if (ch->scan_type.endianness == IIO_BE) + tmp = get_unaligned_be16(outdata); + else + tmp = get_unaligned_le16(outdata); + } else if (byte_for_channel == 3) { + if (ch->scan_type.endianness == IIO_BE) + tmp = get_unaligned_be24(outdata); + else + tmp = get_unaligned_le24(outdata); + } else { + return -EINVAL; + } + *data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1); + + return 0; +} + +int st_sensors_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&sdata->odr_lock); + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto out; + + msleep((sdata->sensor_settings->bootime * 1000) / sdata->odr); + err = st_sensors_read_axis_data(indio_dev, ch, val); + if (err < 0) + goto out; + + *val = *val >> ch->scan_type.shift; + + err = st_sensors_set_enable(indio_dev, false); + +out: + mutex_unlock(&sdata->odr_lock); + iio_device_release_direct(indio_dev); + + return err; +} +EXPORT_SYMBOL_NS(st_sensors_read_info_raw, "IIO_ST_SENSORS"); + +/* + * st_sensors_get_settings_index() - get index of the sensor settings for a + * specific device from list of settings + * @name: device name buffer reference. + * @list: sensor settings list. + * @list_length: length of sensor settings list. + * + * Return: non negative number on success (valid index), + * negative error code otherwise. + */ +int st_sensors_get_settings_index(const char *name, + const struct st_sensor_settings *list, + const int list_length) +{ + int i, n; + + for (i = 0; i < list_length; i++) { + for (n = 0; n < ST_SENSORS_MAX_4WAI; n++) { + if (strcmp(name, list[i].sensors_supported[n]) == 0) + return i; + } + } + + return -ENODEV; +} +EXPORT_SYMBOL_NS(st_sensors_get_settings_index, "IIO_ST_SENSORS"); + +/* + * st_sensors_verify_id() - verify sensor ID (WhoAmI) is matching with the + * expected value + * @indio_dev: IIO device reference. + * + * Return: 0 on success (valid sensor ID), else a negative error code. + */ +int st_sensors_verify_id(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + int wai, err; + + if (sdata->sensor_settings->wai_addr) { + err = regmap_read(sdata->regmap, + sdata->sensor_settings->wai_addr, &wai); + if (err < 0) { + return dev_err_probe(parent, err, + "failed to read Who-Am-I register.\n"); + } + + if (sdata->sensor_settings->wai != wai) { + dev_warn(parent, "%s: WhoAmI mismatch (0x%x).\n", + indio_dev->name, wai); + } + } + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_verify_id, "IIO_ST_SENSORS"); + +ssize_t st_sensors_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sdata->sensor_settings->odr.odr_avl[i].hz == 0) + break; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + sdata->sensor_settings->odr.odr_avl[i].hz); + } + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL_NS(st_sensors_sysfs_sampling_frequency_avail, "IIO_ST_SENSORS"); + +ssize_t st_sensors_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0, q, r; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sdata->sensor_settings->fs.fs_avl[i].num == 0) + break; + + q = sdata->sensor_settings->fs.fs_avl[i].gain / 1000000; + r = sdata->sensor_settings->fs.fs_avl[i].gain % 1000000; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", q, r); + } + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL_NS(st_sensors_sysfs_scale_avail, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_core.h b/drivers/iio/common/st_sensors/st_sensors_core.h new file mode 100644 index 000000000..09f3e602a --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Local functions in the ST Sensors core + */ +#ifndef __ST_SENSORS_CORE_H +#define __ST_SENSORS_CORE_H +struct iio_dev; +int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data); +#endif diff --git a/drivers/iio/common/st_sensors/st_sensors_i2c.c b/drivers/iio/common/st_sensors/st_sensors_i2c.c new file mode 100644 index 000000000..7156302fe --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_i2c.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors i2c library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include + +#define ST_SENSORS_I2C_MULTIREAD 0x80 + +static const struct regmap_config st_sensors_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regmap_config st_sensors_i2c_regmap_multiread_bit_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = ST_SENSORS_I2C_MULTIREAD, +}; + +/* + * st_sensors_i2c_configure() - configure I2C interface + * @indio_dev: IIO device reference. + * @client: i2c client reference. + * + * Return: 0 on success, else a negative error code. + */ +int st_sensors_i2c_configure(struct iio_dev *indio_dev, + struct i2c_client *client) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + const struct regmap_config *config; + + if (sdata->sensor_settings->multi_read_bit) + config = &st_sensors_i2c_regmap_multiread_bit_config; + else + config = &st_sensors_i2c_regmap_config; + + sdata->regmap = devm_regmap_init_i2c(client, config); + if (IS_ERR(sdata->regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap (%ld)\n", + PTR_ERR(sdata->regmap)); + return PTR_ERR(sdata->regmap); + } + + i2c_set_clientdata(client, indio_dev); + + indio_dev->name = client->name; + + sdata->irq = client->irq; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_i2c_configure, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_spi.c b/drivers/iio/common/st_sensors/st_sensors_spi.c new file mode 100644 index 000000000..0da270139 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_spi.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors spi library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define ST_SENSORS_SPI_MULTIREAD 0xc0 + +static const struct regmap_config st_sensors_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regmap_config st_sensors_spi_regmap_multiread_bit_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = ST_SENSORS_SPI_MULTIREAD, +}; + +/* + * st_sensors_is_spi_3_wire() - check if SPI 3-wire mode has been selected + * @spi: spi device reference. + * + * Return: true if SPI 3-wire mode is selected, false otherwise. + */ +static bool st_sensors_is_spi_3_wire(struct spi_device *spi) +{ + struct st_sensors_platform_data *pdata; + struct device *dev = &spi->dev; + + if (device_property_read_bool(dev, "spi-3wire")) + return true; + + pdata = dev_get_platdata(dev); + if (pdata && pdata->spi_3wire) + return true; + + return false; +} + +/* + * st_sensors_configure_spi_3_wire() - configure SPI 3-wire if needed + * @spi: spi device reference. + * @settings: sensor specific settings reference. + * + * Return: 0 on success, else a negative error code. + */ +static int st_sensors_configure_spi_3_wire(struct spi_device *spi, + struct st_sensor_settings *settings) +{ + if (settings->sim.addr) { + u8 buffer[] = { + settings->sim.addr, + settings->sim.value + }; + + return spi_write(spi, buffer, 2); + } + + return 0; +} + +/* + * st_sensors_spi_configure() - configure SPI interface + * @indio_dev: IIO device reference. + * @spi: spi device reference. + * + * Return: 0 on success, else a negative error code. + */ +int st_sensors_spi_configure(struct iio_dev *indio_dev, + struct spi_device *spi) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + const struct regmap_config *config; + int err; + + if (st_sensors_is_spi_3_wire(spi)) { + err = st_sensors_configure_spi_3_wire(spi, + sdata->sensor_settings); + if (err < 0) + return err; + } + + if (sdata->sensor_settings->multi_read_bit) + config = &st_sensors_spi_regmap_multiread_bit_config; + else + config = &st_sensors_spi_regmap_config; + + sdata->regmap = devm_regmap_init_spi(spi, config); + if (IS_ERR(sdata->regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap (%ld)\n", + PTR_ERR(sdata->regmap)); + return PTR_ERR(sdata->regmap); + } + + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->modalias; + + sdata->irq = spi->irq; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_spi_configure, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c new file mode 100644 index 000000000..7b3e9732c --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors trigger library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include "st_sensors_core.h" + +/** + * st_sensors_new_samples_available() - check if more samples came in + * @indio_dev: IIO device reference. + * @sdata: Sensor data. + * + * returns: + * false - no new samples available or read error + * true - new samples available + */ +static bool st_sensors_new_samples_available(struct iio_dev *indio_dev, + struct st_sensor_data *sdata) +{ + int ret, status; + + /* How would I know if I can't check it? */ + if (!sdata->sensor_settings->drdy_irq.stat_drdy.addr) + return true; + + /* No scan mask, no interrupt */ + if (!indio_dev->active_scan_mask) + return false; + + ret = regmap_read(sdata->regmap, + sdata->sensor_settings->drdy_irq.stat_drdy.addr, + &status); + if (ret < 0) { + dev_err(indio_dev->dev.parent, + "error checking samples available\n"); + return false; + } + + return !!(status & sdata->sensor_settings->drdy_irq.stat_drdy.mask); +} + +/** + * st_sensors_irq_handler() - top half of the IRQ-based triggers + * @irq: irq number + * @p: private handler data + */ +static irqreturn_t st_sensors_irq_handler(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* Get the time stamp as close in time as possible */ + sdata->hw_timestamp = iio_get_time_ns(indio_dev); + return IRQ_WAKE_THREAD; +} + +/** + * st_sensors_irq_thread() - bottom half of the IRQ-based triggers + * @irq: irq number + * @p: private handler data + */ +static irqreturn_t st_sensors_irq_thread(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* + * If this trigger is backed by a hardware interrupt and we have a + * status register, check if this IRQ came from us. Notice that + * we will process also if st_sensors_new_samples_available() + * returns negative: if we can't check status, then poll + * unconditionally. + */ + if (sdata->hw_irq_trigger && + st_sensors_new_samples_available(indio_dev, sdata)) { + iio_trigger_poll_nested(p); + } else { + dev_dbg(indio_dev->dev.parent, "spurious IRQ\n"); + return IRQ_NONE; + } + + /* + * If we have proper level IRQs the handler will be re-entered if + * the line is still active, so return here and come back in through + * the top half if need be. + */ + if (!sdata->edge_irq) + return IRQ_HANDLED; + + /* + * If we are using edge IRQs, new samples arrived while processing + * the IRQ and those may be missed unless we pick them here, so poll + * again. If the sensor delivery frequency is very high, this thread + * turns into a polled loop handler. + */ + while (sdata->hw_irq_trigger && + st_sensors_new_samples_available(indio_dev, sdata)) { + dev_dbg(indio_dev->dev.parent, + "more samples came in during polling\n"); + sdata->hw_timestamp = iio_get_time_ns(indio_dev); + iio_trigger_poll_nested(p); + } + + return IRQ_HANDLED; +} + +int st_sensors_allocate_trigger(struct iio_dev *indio_dev, + const struct iio_trigger_ops *trigger_ops) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + unsigned long irq_trig; + int err; + + sdata->trig = devm_iio_trigger_alloc(parent, "%s-trigger", + indio_dev->name); + if (sdata->trig == NULL) { + dev_err(parent, "failed to allocate iio trigger.\n"); + return -ENOMEM; + } + + iio_trigger_set_drvdata(sdata->trig, indio_dev); + sdata->trig->ops = trigger_ops; + + /* + * If the IRQ is triggered on falling edge, we need to mark the + * interrupt as active low, if the hardware supports this. + */ + irq_trig = irq_get_trigger_type(sdata->irq); + switch(irq_trig) { + case IRQF_TRIGGER_FALLING: + case IRQF_TRIGGER_LOW: + if (!sdata->sensor_settings->drdy_irq.addr_ihl) { + dev_err(parent, + "falling/low specified for IRQ but hardware supports only rising/high: will request rising/high\n"); + if (irq_trig == IRQF_TRIGGER_FALLING) + irq_trig = IRQF_TRIGGER_RISING; + if (irq_trig == IRQF_TRIGGER_LOW) + irq_trig = IRQF_TRIGGER_HIGH; + } else { + /* Set up INT active low i.e. falling edge */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.addr_ihl, + sdata->sensor_settings->drdy_irq.mask_ihl, 1); + if (err < 0) + return err; + dev_info(parent, + "interrupts on the falling edge or active low level\n"); + } + break; + case IRQF_TRIGGER_RISING: + dev_info(parent, "interrupts on the rising edge\n"); + break; + case IRQF_TRIGGER_HIGH: + dev_info(parent, "interrupts active high level\n"); + break; + default: + /* This is the most preferred mode, if possible */ + dev_err(parent, + "unsupported IRQ trigger specified (%lx), enforce rising edge\n", irq_trig); + irq_trig = IRQF_TRIGGER_RISING; + } + + /* Tell the interrupt handler that we're dealing with edges */ + if (irq_trig == IRQF_TRIGGER_FALLING || + irq_trig == IRQF_TRIGGER_RISING) { + if (!sdata->sensor_settings->drdy_irq.stat_drdy.addr) { + dev_err(parent, + "edge IRQ not supported w/o stat register.\n"); + return -EOPNOTSUPP; + } + sdata->edge_irq = true; + } else { + /* + * If we're not using edges (i.e. level interrupts) we + * just mask off the IRQ, handle one interrupt, then + * if the line is still low, we return to the + * interrupt handler top half again and start over. + */ + irq_trig |= IRQF_ONESHOT; + } + + /* + * If the interrupt pin is Open Drain, by definition this + * means that the interrupt line may be shared with other + * peripherals. But to do this we also need to have a status + * register and mask to figure out if this sensor was firing + * the IRQ or not, so we can tell the interrupt handle that + * it was "our" interrupt. + */ + if (sdata->int_pin_open_drain && + sdata->sensor_settings->drdy_irq.stat_drdy.addr) + irq_trig |= IRQF_SHARED; + + err = devm_request_threaded_irq(parent, + sdata->irq, + st_sensors_irq_handler, + st_sensors_irq_thread, + irq_trig, + sdata->trig->name, + sdata->trig); + if (err) + return err; + + err = devm_iio_trigger_register(parent, sdata->trig); + if (err < 0) { + dev_err(parent, "failed to register iio trigger.\n"); + return err; + } + indio_dev->trig = iio_trigger_get(sdata->trig); + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_allocate_trigger, "IIO_ST_SENSORS"); + +int st_sensors_validate_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + + if (indio != indio_dev) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_validate_device, "IIO_ST_SENSORS"); -- cgit v1.3.1