diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/iio/common/st_sensors | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to '')
-rw-r--r-- | drivers/iio/common/st_sensors/Kconfig | 15 | ||||
-rw-r--r-- | drivers/iio/common/st_sensors/Makefile | 11 | ||||
-rw-r--r-- | drivers/iio/common/st_sensors/st_sensors_buffer.c | 79 | ||||
-rw-r--r-- | drivers/iio/common/st_sensors/st_sensors_core.c | 664 | ||||
-rw-r--r-- | drivers/iio/common/st_sensors/st_sensors_core.h | 10 | ||||
-rw-r--r-- | drivers/iio/common/st_sensors/st_sensors_i2c.c | 68 | ||||
-rw-r--r-- | drivers/iio/common/st_sensors/st_sensors_spi.c | 120 | ||||
-rw-r--r-- | drivers/iio/common/st_sensors/st_sensors_trigger.c | 242 |
8 files changed, 1209 insertions, 0 deletions
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..e2f108ca9 --- /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 <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/interrupt.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/irqreturn.h> +#include <linux/regmap.h> + +#include <linux/iio/common/st_sensors.h> + + +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..c77d7bdcc --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors core library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/regmap.h> +#include <asm/unaligned.h> +#include <linux/iio/common/st_sensors.h> + +#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, "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(&indio_dev->dev, 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 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(&indio_dev->dev, + "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(&indio_dev->dev, + "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(&indio_dev->dev, + "DRDY on INT2 not available.\n"); + return -EINVAL; + } + sdata->drdy_int_pin = 2; + break; + default: + dev_err(&indio_dev->dev, "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(&indio_dev->dev, + "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 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(indio_dev->dev.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(&indio_dev->dev, "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(&indio_dev->dev, + "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; + + byte_for_channel = DIV_ROUND_UP(ch->scan_type.realbits + + ch->scan_type.shift, 8); + outdata = kmalloc(byte_for_channel, GFP_DMA | GFP_KERNEL); + if (!outdata) + return -ENOMEM; + + err = regmap_bulk_read(sdata->regmap, ch->address, + outdata, byte_for_channel); + if (err < 0) + goto st_sensors_free_memory; + + if (byte_for_channel == 1) + *data = (s8)*outdata; + else if (byte_for_channel == 2) + *data = (s16)get_unaligned_le16(outdata); + else if (byte_for_channel == 3) + *data = (s32)sign_extend32(get_unaligned_le24(outdata), 23); + +st_sensors_free_memory: + kfree(outdata); + + return err; +} + +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); + + err = iio_device_claim_direct_mode(indio_dev); + if (err) + return err; + + 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_mode(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); + int wai, err; + + if (sdata->sensor_settings->wai_addr) { + err = regmap_read(sdata->regmap, + sdata->sensor_settings->wai_addr, &wai); + if (err < 0) { + dev_err(&indio_dev->dev, + "failed to read Who-Am-I register.\n"); + return err; + } + + if (sdata->sensor_settings->wai != wai) { + dev_err(&indio_dev->dev, + "%s: WhoAmI mismatch (0x%x).\n", + indio_dev->name, wai); + return -EINVAL; + } + } + + 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 <denis.ciocca@st.com>"); +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..ee95082c7 --- /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 <denis.ciocca@st.com> + */ + +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/regmap.h> + +#include <linux/iio/common/st_sensors_i2c.h> + +#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 <denis.ciocca@st.com>"); +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..63e302c3f --- /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 <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include <linux/iio/common/st_sensors_spi.h> + +#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 <denis.ciocca@st.com>"); +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..899b640c0 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors trigger library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/iio/common/st_sensors.h> +#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_chained(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_chained(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(&indio_dev->dev, "failed to allocate iio trigger.\n"); + return -ENOMEM; + } + + iio_trigger_set_drvdata(sdata->trig, indio_dev); + sdata->trig->ops = trigger_ops; + + irq_trig = irqd_get_trigger_type(irq_get_irq_data(sdata->irq)); + /* + * If the IRQ is triggered on falling edge, we need to mark the + * interrupt as active low, if the hardware supports this. + */ + switch(irq_trig) { + case IRQF_TRIGGER_FALLING: + case IRQF_TRIGGER_LOW: + if (!sdata->sensor_settings->drdy_irq.addr_ihl) { + dev_err(&indio_dev->dev, + "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(&indio_dev->dev, + "interrupts on the falling edge or active low level\n"); + } + break; + case IRQF_TRIGGER_RISING: + dev_info(&indio_dev->dev, + "interrupts on the rising edge\n"); + break; + case IRQF_TRIGGER_HIGH: + dev_info(&indio_dev->dev, + "interrupts active high level\n"); + break; + default: + /* This is the most preferred mode, if possible */ + dev_err(&indio_dev->dev, + "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(&indio_dev->dev, + "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) { + dev_err(&indio_dev->dev, "failed to request trigger IRQ.\n"); + return err; + } + + err = devm_iio_trigger_register(parent, sdata->trig); + if (err < 0) { + dev_err(&indio_dev->dev, "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); |