diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/input/rmi4/rmi_f01.c | |
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 'drivers/input/rmi4/rmi_f01.c')
-rw-r--r-- | drivers/input/rmi4/rmi_f01.c | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/drivers/input/rmi4/rmi_f01.c b/drivers/input/rmi4/rmi_f01.c new file mode 100644 index 000000000..d7603c50f --- /dev/null +++ b/drivers/input/rmi4/rmi_f01.c @@ -0,0 +1,729 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2016 Synaptics Incorporated + * Copyright (c) 2011 Unixphere + */ + +#include <linux/kernel.h> +#include <linux/rmi.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/of.h> +#include <asm/unaligned.h> +#include "rmi_driver.h" + +#define RMI_PRODUCT_ID_LENGTH 10 +#define RMI_PRODUCT_INFO_LENGTH 2 + +#define RMI_DATE_CODE_LENGTH 3 + +#define PRODUCT_ID_OFFSET 0x10 +#define PRODUCT_INFO_OFFSET 0x1E + + +/* Force a firmware reset of the sensor */ +#define RMI_F01_CMD_DEVICE_RESET 1 + +/* Various F01_RMI_QueryX bits */ + +#define RMI_F01_QRY1_CUSTOM_MAP BIT(0) +#define RMI_F01_QRY1_NON_COMPLIANT BIT(1) +#define RMI_F01_QRY1_HAS_LTS BIT(2) +#define RMI_F01_QRY1_HAS_SENSOR_ID BIT(3) +#define RMI_F01_QRY1_HAS_CHARGER_INP BIT(4) +#define RMI_F01_QRY1_HAS_ADJ_DOZE BIT(5) +#define RMI_F01_QRY1_HAS_ADJ_DOZE_HOFF BIT(6) +#define RMI_F01_QRY1_HAS_QUERY42 BIT(7) + +#define RMI_F01_QRY5_YEAR_MASK 0x1f +#define RMI_F01_QRY6_MONTH_MASK 0x0f +#define RMI_F01_QRY7_DAY_MASK 0x1f + +#define RMI_F01_QRY2_PRODINFO_MASK 0x7f + +#define RMI_F01_BASIC_QUERY_LEN 21 /* From Query 00 through 20 */ + +struct f01_basic_properties { + u8 manufacturer_id; + bool has_lts; + bool has_adjustable_doze; + bool has_adjustable_doze_holdoff; + char dom[11]; /* YYYY/MM/DD + '\0' */ + u8 product_id[RMI_PRODUCT_ID_LENGTH + 1]; + u16 productinfo; + u32 firmware_id; + u32 package_id; +}; + +/* F01 device status bits */ + +/* Most recent device status event */ +#define RMI_F01_STATUS_CODE(status) ((status) & 0x0f) +/* The device has lost its configuration for some reason. */ +#define RMI_F01_STATUS_UNCONFIGURED(status) (!!((status) & 0x80)) +/* The device is in bootloader mode */ +#define RMI_F01_STATUS_BOOTLOADER(status) ((status) & 0x40) + +/* Control register bits */ + +/* + * Sleep mode controls power management on the device and affects all + * functions of the device. + */ +#define RMI_F01_CTRL0_SLEEP_MODE_MASK 0x03 + +#define RMI_SLEEP_MODE_NORMAL 0x00 +#define RMI_SLEEP_MODE_SENSOR_SLEEP 0x01 +#define RMI_SLEEP_MODE_RESERVED0 0x02 +#define RMI_SLEEP_MODE_RESERVED1 0x03 + +/* + * This bit disables whatever sleep mode may be selected by the sleep_mode + * field and forces the device to run at full power without sleeping. + */ +#define RMI_F01_CTRL0_NOSLEEP_BIT BIT(2) + +/* + * When this bit is set, the touch controller employs a noise-filtering + * algorithm designed for use with a connected battery charger. + */ +#define RMI_F01_CTRL0_CHARGER_BIT BIT(5) + +/* + * Sets the report rate for the device. The effect of this setting is + * highly product dependent. Check the spec sheet for your particular + * touch sensor. + */ +#define RMI_F01_CTRL0_REPORTRATE_BIT BIT(6) + +/* + * Written by the host as an indicator that the device has been + * successfully configured. + */ +#define RMI_F01_CTRL0_CONFIGURED_BIT BIT(7) + +/** + * struct f01_device_control - controls basic sensor functions + * + * @ctrl0: see the bit definitions above. + * @doze_interval: controls the interval between checks for finger presence + * when the touch sensor is in doze mode, in units of 10ms. + * @wakeup_threshold: controls the capacitance threshold at which the touch + * sensor will decide to wake up from that low power state. + * @doze_holdoff: controls how long the touch sensor waits after the last + * finger lifts before entering the doze state, in units of 100ms. + */ +struct f01_device_control { + u8 ctrl0; + u8 doze_interval; + u8 wakeup_threshold; + u8 doze_holdoff; +}; + +struct f01_data { + struct f01_basic_properties properties; + struct f01_device_control device_control; + + u16 doze_interval_addr; + u16 wakeup_threshold_addr; + u16 doze_holdoff_addr; + + bool suspended; + bool old_nosleep; + + unsigned int num_of_irq_regs; +}; + +static int rmi_f01_read_properties(struct rmi_device *rmi_dev, + u16 query_base_addr, + struct f01_basic_properties *props) +{ + u8 queries[RMI_F01_BASIC_QUERY_LEN]; + int ret; + int query_offset = query_base_addr; + bool has_ds4_queries = false; + bool has_query42 = false; + bool has_sensor_id = false; + bool has_package_id_query = false; + bool has_build_id_query = false; + u16 prod_info_addr; + u8 ds4_query_len; + + ret = rmi_read_block(rmi_dev, query_offset, + queries, RMI_F01_BASIC_QUERY_LEN); + if (ret) { + dev_err(&rmi_dev->dev, + "Failed to read device query registers: %d\n", ret); + return ret; + } + + prod_info_addr = query_offset + 17; + query_offset += RMI_F01_BASIC_QUERY_LEN; + + /* Now parse what we got */ + props->manufacturer_id = queries[0]; + + props->has_lts = queries[1] & RMI_F01_QRY1_HAS_LTS; + props->has_adjustable_doze = + queries[1] & RMI_F01_QRY1_HAS_ADJ_DOZE; + props->has_adjustable_doze_holdoff = + queries[1] & RMI_F01_QRY1_HAS_ADJ_DOZE_HOFF; + has_query42 = queries[1] & RMI_F01_QRY1_HAS_QUERY42; + has_sensor_id = queries[1] & RMI_F01_QRY1_HAS_SENSOR_ID; + + snprintf(props->dom, sizeof(props->dom), "20%02d/%02d/%02d", + queries[5] & RMI_F01_QRY5_YEAR_MASK, + queries[6] & RMI_F01_QRY6_MONTH_MASK, + queries[7] & RMI_F01_QRY7_DAY_MASK); + + memcpy(props->product_id, &queries[11], + RMI_PRODUCT_ID_LENGTH); + props->product_id[RMI_PRODUCT_ID_LENGTH] = '\0'; + + props->productinfo = + ((queries[2] & RMI_F01_QRY2_PRODINFO_MASK) << 7) | + (queries[3] & RMI_F01_QRY2_PRODINFO_MASK); + + if (has_sensor_id) + query_offset++; + + if (has_query42) { + ret = rmi_read(rmi_dev, query_offset, queries); + if (ret) { + dev_err(&rmi_dev->dev, + "Failed to read query 42 register: %d\n", ret); + return ret; + } + + has_ds4_queries = !!(queries[0] & BIT(0)); + query_offset++; + } + + if (has_ds4_queries) { + ret = rmi_read(rmi_dev, query_offset, &ds4_query_len); + if (ret) { + dev_err(&rmi_dev->dev, + "Failed to read DS4 queries length: %d\n", ret); + return ret; + } + query_offset++; + + if (ds4_query_len > 0) { + ret = rmi_read(rmi_dev, query_offset, queries); + if (ret) { + dev_err(&rmi_dev->dev, + "Failed to read DS4 queries: %d\n", + ret); + return ret; + } + + has_package_id_query = !!(queries[0] & BIT(0)); + has_build_id_query = !!(queries[0] & BIT(1)); + } + + if (has_package_id_query) { + ret = rmi_read_block(rmi_dev, prod_info_addr, + queries, sizeof(__le64)); + if (ret) { + dev_err(&rmi_dev->dev, + "Failed to read package info: %d\n", + ret); + return ret; + } + + props->package_id = get_unaligned_le64(queries); + prod_info_addr++; + } + + if (has_build_id_query) { + ret = rmi_read_block(rmi_dev, prod_info_addr, queries, + 3); + if (ret) { + dev_err(&rmi_dev->dev, + "Failed to read product info: %d\n", + ret); + return ret; + } + + props->firmware_id = queries[1] << 8 | queries[0]; + props->firmware_id += queries[2] * 65536; + } + } + + return 0; +} + +const char *rmi_f01_get_product_ID(struct rmi_function *fn) +{ + struct f01_data *f01 = dev_get_drvdata(&fn->dev); + + return f01->properties.product_id; +} + +static ssize_t rmi_driver_manufacturer_id_show(struct device *dev, + struct device_attribute *dattr, + char *buf) +{ + struct rmi_driver_data *data = dev_get_drvdata(dev); + struct f01_data *f01 = dev_get_drvdata(&data->f01_container->dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + f01->properties.manufacturer_id); +} + +static DEVICE_ATTR(manufacturer_id, 0444, + rmi_driver_manufacturer_id_show, NULL); + +static ssize_t rmi_driver_dom_show(struct device *dev, + struct device_attribute *dattr, char *buf) +{ + struct rmi_driver_data *data = dev_get_drvdata(dev); + struct f01_data *f01 = dev_get_drvdata(&data->f01_container->dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", f01->properties.dom); +} + +static DEVICE_ATTR(date_of_manufacture, 0444, rmi_driver_dom_show, NULL); + +static ssize_t rmi_driver_product_id_show(struct device *dev, + struct device_attribute *dattr, + char *buf) +{ + struct rmi_driver_data *data = dev_get_drvdata(dev); + struct f01_data *f01 = dev_get_drvdata(&data->f01_container->dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", f01->properties.product_id); +} + +static DEVICE_ATTR(product_id, 0444, rmi_driver_product_id_show, NULL); + +static ssize_t rmi_driver_firmware_id_show(struct device *dev, + struct device_attribute *dattr, + char *buf) +{ + struct rmi_driver_data *data = dev_get_drvdata(dev); + struct f01_data *f01 = dev_get_drvdata(&data->f01_container->dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", f01->properties.firmware_id); +} + +static DEVICE_ATTR(firmware_id, 0444, rmi_driver_firmware_id_show, NULL); + +static ssize_t rmi_driver_package_id_show(struct device *dev, + struct device_attribute *dattr, + char *buf) +{ + struct rmi_driver_data *data = dev_get_drvdata(dev); + struct f01_data *f01 = dev_get_drvdata(&data->f01_container->dev); + + u32 package_id = f01->properties.package_id; + + return scnprintf(buf, PAGE_SIZE, "%04x.%04x\n", + package_id & 0xffff, (package_id >> 16) & 0xffff); +} + +static DEVICE_ATTR(package_id, 0444, rmi_driver_package_id_show, NULL); + +static struct attribute *rmi_f01_attrs[] = { + &dev_attr_manufacturer_id.attr, + &dev_attr_date_of_manufacture.attr, + &dev_attr_product_id.attr, + &dev_attr_firmware_id.attr, + &dev_attr_package_id.attr, + NULL +}; + +static const struct attribute_group rmi_f01_attr_group = { + .attrs = rmi_f01_attrs, +}; + +#ifdef CONFIG_OF +static int rmi_f01_of_probe(struct device *dev, + struct rmi_device_platform_data *pdata) +{ + int retval; + u32 val; + + retval = rmi_of_property_read_u32(dev, + (u32 *)&pdata->power_management.nosleep, + "syna,nosleep-mode", 1); + if (retval) + return retval; + + retval = rmi_of_property_read_u32(dev, &val, + "syna,wakeup-threshold", 1); + if (retval) + return retval; + + pdata->power_management.wakeup_threshold = val; + + retval = rmi_of_property_read_u32(dev, &val, + "syna,doze-holdoff-ms", 1); + if (retval) + return retval; + + pdata->power_management.doze_holdoff = val * 100; + + retval = rmi_of_property_read_u32(dev, &val, + "syna,doze-interval-ms", 1); + if (retval) + return retval; + + pdata->power_management.doze_interval = val / 10; + + return 0; +} +#else +static inline int rmi_f01_of_probe(struct device *dev, + struct rmi_device_platform_data *pdata) +{ + return -ENODEV; +} +#endif + +static int rmi_f01_probe(struct rmi_function *fn) +{ + struct rmi_device *rmi_dev = fn->rmi_dev; + struct rmi_driver_data *driver_data = dev_get_drvdata(&rmi_dev->dev); + struct rmi_device_platform_data *pdata = rmi_get_platform_data(rmi_dev); + struct f01_data *f01; + int error; + u16 ctrl_base_addr = fn->fd.control_base_addr; + u8 device_status; + u8 temp; + + if (fn->dev.of_node) { + error = rmi_f01_of_probe(&fn->dev, pdata); + if (error) + return error; + } + + f01 = devm_kzalloc(&fn->dev, sizeof(struct f01_data), GFP_KERNEL); + if (!f01) + return -ENOMEM; + + f01->num_of_irq_regs = driver_data->num_of_irq_regs; + + /* + * Set the configured bit and (optionally) other important stuff + * in the device control register. + */ + + error = rmi_read(rmi_dev, fn->fd.control_base_addr, + &f01->device_control.ctrl0); + if (error) { + dev_err(&fn->dev, "Failed to read F01 control: %d\n", error); + return error; + } + + switch (pdata->power_management.nosleep) { + case RMI_REG_STATE_DEFAULT: + break; + case RMI_REG_STATE_OFF: + f01->device_control.ctrl0 &= ~RMI_F01_CTRL0_NOSLEEP_BIT; + break; + case RMI_REG_STATE_ON: + f01->device_control.ctrl0 |= RMI_F01_CTRL0_NOSLEEP_BIT; + break; + } + + /* + * Sleep mode might be set as a hangover from a system crash or + * reboot without power cycle. If so, clear it so the sensor + * is certain to function. + */ + if ((f01->device_control.ctrl0 & RMI_F01_CTRL0_SLEEP_MODE_MASK) != + RMI_SLEEP_MODE_NORMAL) { + dev_warn(&fn->dev, + "WARNING: Non-zero sleep mode found. Clearing...\n"); + f01->device_control.ctrl0 &= ~RMI_F01_CTRL0_SLEEP_MODE_MASK; + } + + f01->device_control.ctrl0 |= RMI_F01_CTRL0_CONFIGURED_BIT; + + error = rmi_write(rmi_dev, fn->fd.control_base_addr, + f01->device_control.ctrl0); + if (error) { + dev_err(&fn->dev, "Failed to write F01 control: %d\n", error); + return error; + } + + /* Dummy read in order to clear irqs */ + error = rmi_read(rmi_dev, fn->fd.data_base_addr + 1, &temp); + if (error < 0) { + dev_err(&fn->dev, "Failed to read Interrupt Status.\n"); + return error; + } + + error = rmi_f01_read_properties(rmi_dev, fn->fd.query_base_addr, + &f01->properties); + if (error < 0) { + dev_err(&fn->dev, "Failed to read F01 properties.\n"); + return error; + } + + dev_info(&fn->dev, "found RMI device, manufacturer: %s, product: %s, fw id: %d\n", + f01->properties.manufacturer_id == 1 ? "Synaptics" : "unknown", + f01->properties.product_id, f01->properties.firmware_id); + + /* Advance to interrupt control registers, then skip over them. */ + ctrl_base_addr++; + ctrl_base_addr += f01->num_of_irq_regs; + + /* read control register */ + if (f01->properties.has_adjustable_doze) { + f01->doze_interval_addr = ctrl_base_addr; + ctrl_base_addr++; + + if (pdata->power_management.doze_interval) { + f01->device_control.doze_interval = + pdata->power_management.doze_interval; + error = rmi_write(rmi_dev, f01->doze_interval_addr, + f01->device_control.doze_interval); + if (error) { + dev_err(&fn->dev, + "Failed to configure F01 doze interval register: %d\n", + error); + return error; + } + } else { + error = rmi_read(rmi_dev, f01->doze_interval_addr, + &f01->device_control.doze_interval); + if (error) { + dev_err(&fn->dev, + "Failed to read F01 doze interval register: %d\n", + error); + return error; + } + } + + f01->wakeup_threshold_addr = ctrl_base_addr; + ctrl_base_addr++; + + if (pdata->power_management.wakeup_threshold) { + f01->device_control.wakeup_threshold = + pdata->power_management.wakeup_threshold; + error = rmi_write(rmi_dev, f01->wakeup_threshold_addr, + f01->device_control.wakeup_threshold); + if (error) { + dev_err(&fn->dev, + "Failed to configure F01 wakeup threshold register: %d\n", + error); + return error; + } + } else { + error = rmi_read(rmi_dev, f01->wakeup_threshold_addr, + &f01->device_control.wakeup_threshold); + if (error < 0) { + dev_err(&fn->dev, + "Failed to read F01 wakeup threshold register: %d\n", + error); + return error; + } + } + } + + if (f01->properties.has_lts) + ctrl_base_addr++; + + if (f01->properties.has_adjustable_doze_holdoff) { + f01->doze_holdoff_addr = ctrl_base_addr; + ctrl_base_addr++; + + if (pdata->power_management.doze_holdoff) { + f01->device_control.doze_holdoff = + pdata->power_management.doze_holdoff; + error = rmi_write(rmi_dev, f01->doze_holdoff_addr, + f01->device_control.doze_holdoff); + if (error) { + dev_err(&fn->dev, + "Failed to configure F01 doze holdoff register: %d\n", + error); + return error; + } + } else { + error = rmi_read(rmi_dev, f01->doze_holdoff_addr, + &f01->device_control.doze_holdoff); + if (error) { + dev_err(&fn->dev, + "Failed to read F01 doze holdoff register: %d\n", + error); + return error; + } + } + } + + error = rmi_read(rmi_dev, fn->fd.data_base_addr, &device_status); + if (error < 0) { + dev_err(&fn->dev, + "Failed to read device status: %d\n", error); + return error; + } + + if (RMI_F01_STATUS_UNCONFIGURED(device_status)) { + dev_err(&fn->dev, + "Device was reset during configuration process, status: %#02x!\n", + RMI_F01_STATUS_CODE(device_status)); + return -EINVAL; + } + + dev_set_drvdata(&fn->dev, f01); + + error = sysfs_create_group(&fn->rmi_dev->dev.kobj, &rmi_f01_attr_group); + if (error) + dev_warn(&fn->dev, "Failed to create sysfs group: %d\n", error); + + return 0; +} + +static void rmi_f01_remove(struct rmi_function *fn) +{ + /* Note that the bus device is used, not the F01 device */ + sysfs_remove_group(&fn->rmi_dev->dev.kobj, &rmi_f01_attr_group); +} + +static int rmi_f01_config(struct rmi_function *fn) +{ + struct f01_data *f01 = dev_get_drvdata(&fn->dev); + int error; + + error = rmi_write(fn->rmi_dev, fn->fd.control_base_addr, + f01->device_control.ctrl0); + if (error) { + dev_err(&fn->dev, + "Failed to write device_control register: %d\n", error); + return error; + } + + if (f01->properties.has_adjustable_doze) { + error = rmi_write(fn->rmi_dev, f01->doze_interval_addr, + f01->device_control.doze_interval); + if (error) { + dev_err(&fn->dev, + "Failed to write doze interval: %d\n", error); + return error; + } + + error = rmi_write_block(fn->rmi_dev, + f01->wakeup_threshold_addr, + &f01->device_control.wakeup_threshold, + sizeof(u8)); + if (error) { + dev_err(&fn->dev, + "Failed to write wakeup threshold: %d\n", + error); + return error; + } + } + + if (f01->properties.has_adjustable_doze_holdoff) { + error = rmi_write(fn->rmi_dev, f01->doze_holdoff_addr, + f01->device_control.doze_holdoff); + if (error) { + dev_err(&fn->dev, + "Failed to write doze holdoff: %d\n", error); + return error; + } + } + + return 0; +} + +static int rmi_f01_suspend(struct rmi_function *fn) +{ + struct f01_data *f01 = dev_get_drvdata(&fn->dev); + int error; + + f01->old_nosleep = + f01->device_control.ctrl0 & RMI_F01_CTRL0_NOSLEEP_BIT; + f01->device_control.ctrl0 &= ~RMI_F01_CTRL0_NOSLEEP_BIT; + + f01->device_control.ctrl0 &= ~RMI_F01_CTRL0_SLEEP_MODE_MASK; + if (device_may_wakeup(fn->rmi_dev->xport->dev)) + f01->device_control.ctrl0 |= RMI_SLEEP_MODE_RESERVED1; + else + f01->device_control.ctrl0 |= RMI_SLEEP_MODE_SENSOR_SLEEP; + + error = rmi_write(fn->rmi_dev, fn->fd.control_base_addr, + f01->device_control.ctrl0); + if (error) { + dev_err(&fn->dev, "Failed to write sleep mode: %d.\n", error); + if (f01->old_nosleep) + f01->device_control.ctrl0 |= RMI_F01_CTRL0_NOSLEEP_BIT; + f01->device_control.ctrl0 &= ~RMI_F01_CTRL0_SLEEP_MODE_MASK; + f01->device_control.ctrl0 |= RMI_SLEEP_MODE_NORMAL; + return error; + } + + return 0; +} + +static int rmi_f01_resume(struct rmi_function *fn) +{ + struct f01_data *f01 = dev_get_drvdata(&fn->dev); + int error; + + if (f01->old_nosleep) + f01->device_control.ctrl0 |= RMI_F01_CTRL0_NOSLEEP_BIT; + + f01->device_control.ctrl0 &= ~RMI_F01_CTRL0_SLEEP_MODE_MASK; + f01->device_control.ctrl0 |= RMI_SLEEP_MODE_NORMAL; + + error = rmi_write(fn->rmi_dev, fn->fd.control_base_addr, + f01->device_control.ctrl0); + if (error) { + dev_err(&fn->dev, + "Failed to restore normal operation: %d.\n", error); + return error; + } + + return 0; +} + +static irqreturn_t rmi_f01_attention(int irq, void *ctx) +{ + struct rmi_function *fn = ctx; + struct rmi_device *rmi_dev = fn->rmi_dev; + int error; + u8 device_status; + + error = rmi_read(rmi_dev, fn->fd.data_base_addr, &device_status); + if (error) { + dev_err(&fn->dev, + "Failed to read device status: %d.\n", error); + return IRQ_RETVAL(error); + } + + if (RMI_F01_STATUS_BOOTLOADER(device_status)) + dev_warn(&fn->dev, + "Device in bootloader mode, please update firmware\n"); + + if (RMI_F01_STATUS_UNCONFIGURED(device_status)) { + dev_warn(&fn->dev, "Device reset detected.\n"); + error = rmi_dev->driver->reset_handler(rmi_dev); + if (error) { + dev_err(&fn->dev, "Device reset failed: %d\n", error); + return IRQ_RETVAL(error); + } + } + + return IRQ_HANDLED; +} + +struct rmi_function_handler rmi_f01_handler = { + .driver = { + .name = "rmi4_f01", + /* + * Do not allow user unbinding F01 as it is critical + * function. + */ + .suppress_bind_attrs = true, + }, + .func = 0x01, + .probe = rmi_f01_probe, + .remove = rmi_f01_remove, + .config = rmi_f01_config, + .attention = rmi_f01_attention, + .suspend = rmi_f01_suspend, + .resume = rmi_f01_resume, +}; |