diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/hid/hid-rmi.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/hid/hid-rmi.c')
-rw-r--r-- | drivers/hid/hid-rmi.c | 788 |
1 files changed, 788 insertions, 0 deletions
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c new file mode 100644 index 000000000..84e7ba531 --- /dev/null +++ b/drivers/hid/hid-rmi.c @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2013 Andrew Duggan <aduggan@synaptics.com> + * Copyright (c) 2013 Synaptics Incorporated + * Copyright (c) 2014 Benjamin Tissoires <benjamin.tissoires@gmail.com> + * Copyright (c) 2014 Red Hat, Inc + */ + +#include <linux/kernel.h> +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/rmi.h> +#include "hid-ids.h" + +#define RMI_MOUSE_REPORT_ID 0x01 /* Mouse emulation Report */ +#define RMI_WRITE_REPORT_ID 0x09 /* Output Report */ +#define RMI_READ_ADDR_REPORT_ID 0x0a /* Output Report */ +#define RMI_READ_DATA_REPORT_ID 0x0b /* Input Report */ +#define RMI_ATTN_REPORT_ID 0x0c /* Input Report */ +#define RMI_SET_RMI_MODE_REPORT_ID 0x0f /* Feature Report */ + +/* flags */ +#define RMI_READ_REQUEST_PENDING 0 +#define RMI_READ_DATA_PENDING 1 +#define RMI_STARTED 2 + +/* device flags */ +#define RMI_DEVICE BIT(0) +#define RMI_DEVICE_HAS_PHYS_BUTTONS BIT(1) +#define RMI_DEVICE_OUTPUT_SET_REPORT BIT(2) + +/* + * retrieve the ctrl registers + * the ctrl register has a size of 20 but a fw bug split it into 16 + 4, + * and there is no way to know if the first 20 bytes are here or not. + * We use only the first 12 bytes, so get only them. + */ +#define RMI_F11_CTRL_REG_COUNT 12 + +enum rmi_mode_type { + RMI_MODE_OFF = 0, + RMI_MODE_ATTN_REPORTS = 1, + RMI_MODE_NO_PACKED_ATTN_REPORTS = 2, +}; + +/** + * struct rmi_data - stores information for hid communication + * + * @page_mutex: Locks current page to avoid changing pages in unexpected ways. + * @page: Keeps track of the current virtual page + * @xport: transport device to be registered with the RMI4 core. + * + * @wait: Used for waiting for read data + * + * @writeReport: output buffer when writing RMI registers + * @readReport: input buffer when reading RMI registers + * + * @input_report_size: size of an input report (advertised by HID) + * @output_report_size: size of an output report (advertised by HID) + * + * @flags: flags for the current device (started, reading, etc...) + * + * @reset_work: worker which will be called in case of a mouse report + * @hdev: pointer to the struct hid_device + * + * @device_flags: flags which describe the device + * + * @domain: the IRQ domain allocated for this RMI4 device + * @rmi_irq: the irq that will be used to generate events to rmi-core + */ +struct rmi_data { + struct mutex page_mutex; + int page; + struct rmi_transport_dev xport; + + wait_queue_head_t wait; + + u8 *writeReport; + u8 *readReport; + + u32 input_report_size; + u32 output_report_size; + + unsigned long flags; + + struct work_struct reset_work; + struct hid_device *hdev; + + unsigned long device_flags; + + struct irq_domain *domain; + int rmi_irq; +}; + +#define RMI_PAGE(addr) (((addr) >> 8) & 0xff) + +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len); + +/** + * rmi_set_page - Set RMI page + * @hdev: The pointer to the hid_device struct + * @page: The new page address. + * + * RMI devices have 16-bit addressing, but some of the physical + * implementations (like SMBus) only have 8-bit addressing. So RMI implements + * a page address at 0xff of every page so we can reliable page addresses + * every 256 registers. + * + * The page_mutex lock must be held when this function is entered. + * + * Returns zero on success, non-zero on failure. + */ +static int rmi_set_page(struct hid_device *hdev, u8 page) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + int retval; + + data->writeReport[0] = RMI_WRITE_REPORT_ID; + data->writeReport[1] = 1; + data->writeReport[2] = 0xFF; + data->writeReport[4] = page; + + retval = rmi_write_report(hdev, data->writeReport, + data->output_report_size); + if (retval != data->output_report_size) { + dev_err(&hdev->dev, + "%s: set page failed: %d.", __func__, retval); + return retval; + } + + data->page = page; + return 0; +} + +static int rmi_set_mode(struct hid_device *hdev, u8 mode) +{ + int ret; + const u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode}; + u8 *buf; + + buf = kmemdup(txbuf, sizeof(txbuf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, buf, + sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + kfree(buf); + if (ret < 0) { + dev_err(&hdev->dev, "unable to set rmi mode to %d (%d)\n", mode, + ret); + return ret; + } + + return 0; +} + +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + int ret; + + if (data->device_flags & RMI_DEVICE_OUTPUT_SET_REPORT) { + /* + * Talk to device by using SET_REPORT requests instead. + */ + ret = hid_hw_raw_request(hdev, report[0], report, + len, HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + } else { + ret = hid_hw_output_report(hdev, (void *)report, len); + } + + if (ret < 0) { + dev_err(&hdev->dev, "failed to write hid report (%d)\n", ret); + return ret; + } + + return ret; +} + +static int rmi_hid_read_block(struct rmi_transport_dev *xport, u16 addr, + void *buf, size_t len) +{ + struct rmi_data *data = container_of(xport, struct rmi_data, xport); + struct hid_device *hdev = data->hdev; + int ret; + int bytes_read; + int bytes_needed; + int retries; + int read_input_count; + + mutex_lock(&data->page_mutex); + + if (RMI_PAGE(addr) != data->page) { + ret = rmi_set_page(hdev, RMI_PAGE(addr)); + if (ret < 0) + goto exit; + } + + for (retries = 5; retries > 0; retries--) { + data->writeReport[0] = RMI_READ_ADDR_REPORT_ID; + data->writeReport[1] = 0; /* old 1 byte read count */ + data->writeReport[2] = addr & 0xFF; + data->writeReport[3] = (addr >> 8) & 0xFF; + data->writeReport[4] = len & 0xFF; + data->writeReport[5] = (len >> 8) & 0xFF; + + set_bit(RMI_READ_REQUEST_PENDING, &data->flags); + + ret = rmi_write_report(hdev, data->writeReport, + data->output_report_size); + if (ret != data->output_report_size) { + dev_err(&hdev->dev, + "failed to write request output report (%d)\n", + ret); + goto exit; + } + + bytes_read = 0; + bytes_needed = len; + while (bytes_read < len) { + if (!wait_event_timeout(data->wait, + test_bit(RMI_READ_DATA_PENDING, &data->flags), + msecs_to_jiffies(1000))) { + hid_warn(hdev, "%s: timeout elapsed\n", + __func__); + ret = -EAGAIN; + break; + } + + read_input_count = data->readReport[1]; + memcpy(buf + bytes_read, &data->readReport[2], + min(read_input_count, bytes_needed)); + + bytes_read += read_input_count; + bytes_needed -= read_input_count; + clear_bit(RMI_READ_DATA_PENDING, &data->flags); + } + + if (ret >= 0) { + ret = 0; + break; + } + } + +exit: + clear_bit(RMI_READ_REQUEST_PENDING, &data->flags); + mutex_unlock(&data->page_mutex); + return ret; +} + +static int rmi_hid_write_block(struct rmi_transport_dev *xport, u16 addr, + const void *buf, size_t len) +{ + struct rmi_data *data = container_of(xport, struct rmi_data, xport); + struct hid_device *hdev = data->hdev; + int ret; + + mutex_lock(&data->page_mutex); + + if (RMI_PAGE(addr) != data->page) { + ret = rmi_set_page(hdev, RMI_PAGE(addr)); + if (ret < 0) + goto exit; + } + + data->writeReport[0] = RMI_WRITE_REPORT_ID; + data->writeReport[1] = len; + data->writeReport[2] = addr & 0xFF; + data->writeReport[3] = (addr >> 8) & 0xFF; + memcpy(&data->writeReport[4], buf, len); + + ret = rmi_write_report(hdev, data->writeReport, + data->output_report_size); + if (ret < 0) { + dev_err(&hdev->dev, + "failed to write request output report (%d)\n", + ret); + goto exit; + } + ret = 0; + +exit: + mutex_unlock(&data->page_mutex); + return ret; +} + +static int rmi_reset_attn_mode(struct hid_device *hdev) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + struct rmi_device *rmi_dev = data->xport.rmi_dev; + int ret; + + ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS); + if (ret) + return ret; + + if (test_bit(RMI_STARTED, &data->flags)) + ret = rmi_dev->driver->reset_handler(rmi_dev); + + return ret; +} + +static void rmi_reset_work(struct work_struct *work) +{ + struct rmi_data *hdata = container_of(work, struct rmi_data, + reset_work); + + /* switch the device to RMI if we receive a generic mouse report */ + rmi_reset_attn_mode(hdata->hdev); +} + +static int rmi_input_event(struct hid_device *hdev, u8 *data, int size) +{ + struct rmi_data *hdata = hid_get_drvdata(hdev); + struct rmi_device *rmi_dev = hdata->xport.rmi_dev; + unsigned long flags; + + if (!(test_bit(RMI_STARTED, &hdata->flags))) + return 0; + + pm_wakeup_event(hdev->dev.parent, 0); + + local_irq_save(flags); + + rmi_set_attn_data(rmi_dev, data[1], &data[2], size - 2); + + generic_handle_irq(hdata->rmi_irq); + + local_irq_restore(flags); + + return 1; +} + +static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size) +{ + struct rmi_data *hdata = hid_get_drvdata(hdev); + + if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) { + hid_dbg(hdev, "no read request pending\n"); + return 0; + } + + memcpy(hdata->readReport, data, min((u32)size, hdata->input_report_size)); + set_bit(RMI_READ_DATA_PENDING, &hdata->flags); + wake_up(&hdata->wait); + + return 1; +} + +static int rmi_check_sanity(struct hid_device *hdev, u8 *data, int size) +{ + int valid_size = size; + /* + * On the Dell XPS 13 9333, the bus sometimes get confused and fills + * the report with a sentinel value "ff". Synaptics told us that such + * behavior does not comes from the touchpad itself, so we filter out + * such reports here. + */ + + while ((data[valid_size - 1] == 0xff) && valid_size > 0) + valid_size--; + + return valid_size; +} + +static int rmi_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct rmi_data *hdata = hid_get_drvdata(hdev); + + if (!(hdata->device_flags & RMI_DEVICE)) + return 0; + + size = rmi_check_sanity(hdev, data, size); + if (size < 2) + return 0; + + switch (data[0]) { + case RMI_READ_DATA_REPORT_ID: + return rmi_read_data_event(hdev, data, size); + case RMI_ATTN_REPORT_ID: + return rmi_input_event(hdev, data, size); + default: + return 1; + } + + return 0; +} + +static int rmi_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + + if ((data->device_flags & RMI_DEVICE) && + (field->application == HID_GD_POINTER || + field->application == HID_GD_MOUSE)) { + if (data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS) { + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) + return 0; + + if ((usage->hid == HID_GD_X || usage->hid == HID_GD_Y) + && !value) + return 1; + } + + schedule_work(&data->reset_work); + return 1; + } + + return 0; +} + +static void rmi_report(struct hid_device *hid, struct hid_report *report) +{ + struct hid_field *field = report->field[0]; + + if (!(hid->claimed & HID_CLAIMED_INPUT)) + return; + + switch (report->id) { + case RMI_READ_DATA_REPORT_ID: + case RMI_ATTN_REPORT_ID: + return; + } + + if (field && field->hidinput && field->hidinput->input) + input_sync(field->hidinput->input); +} + +#ifdef CONFIG_PM +static int rmi_suspend(struct hid_device *hdev, pm_message_t message) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + struct rmi_device *rmi_dev = data->xport.rmi_dev; + int ret; + + if (!(data->device_flags & RMI_DEVICE)) + return 0; + + ret = rmi_driver_suspend(rmi_dev, false); + if (ret) { + hid_warn(hdev, "Failed to suspend device: %d\n", ret); + return ret; + } + + return 0; +} + +static int rmi_post_resume(struct hid_device *hdev) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + struct rmi_device *rmi_dev = data->xport.rmi_dev; + int ret; + + if (!(data->device_flags & RMI_DEVICE)) + return 0; + + /* Make sure the HID device is ready to receive events */ + ret = hid_hw_open(hdev); + if (ret) + return ret; + + ret = rmi_reset_attn_mode(hdev); + if (ret) + goto out; + + ret = rmi_driver_resume(rmi_dev, false); + if (ret) { + hid_warn(hdev, "Failed to resume device: %d\n", ret); + goto out; + } + +out: + hid_hw_close(hdev); + return ret; +} +#endif /* CONFIG_PM */ + +static int rmi_hid_reset(struct rmi_transport_dev *xport, u16 reset_addr) +{ + struct rmi_data *data = container_of(xport, struct rmi_data, xport); + struct hid_device *hdev = data->hdev; + + return rmi_reset_attn_mode(hdev); +} + +static int rmi_input_configured(struct hid_device *hdev, struct hid_input *hi) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + struct input_dev *input = hi->input; + int ret = 0; + + if (!(data->device_flags & RMI_DEVICE)) + return 0; + + data->xport.input = input; + + hid_dbg(hdev, "Opening low level driver\n"); + ret = hid_hw_open(hdev); + if (ret) + return ret; + + /* Allow incoming hid reports */ + hid_device_io_start(hdev); + + ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS); + if (ret < 0) { + dev_err(&hdev->dev, "failed to set rmi mode\n"); + goto exit; + } + + ret = rmi_set_page(hdev, 0); + if (ret < 0) { + dev_err(&hdev->dev, "failed to set page select to 0.\n"); + goto exit; + } + + ret = rmi_register_transport_device(&data->xport); + if (ret < 0) { + dev_err(&hdev->dev, "failed to register transport driver\n"); + goto exit; + } + + set_bit(RMI_STARTED, &data->flags); + +exit: + hid_device_io_stop(hdev); + hid_hw_close(hdev); + return ret; +} + +static int rmi_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + struct rmi_data *data = hid_get_drvdata(hdev); + + /* + * we want to make HID ignore the advertised HID collection + * for RMI deivces + */ + if (data->device_flags & RMI_DEVICE) { + if ((data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS) && + ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)) + return 0; + + return -1; + } + + return 0; +} + +static int rmi_check_valid_report_id(struct hid_device *hdev, unsigned type, + unsigned id, struct hid_report **report) +{ + int i; + + *report = hdev->report_enum[type].report_id_hash[id]; + if (*report) { + for (i = 0; i < (*report)->maxfield; i++) { + unsigned app = (*report)->field[i]->application; + if ((app & HID_USAGE_PAGE) >= HID_UP_MSVENDOR) + return 1; + } + } + + return 0; +} + +static struct rmi_device_platform_data rmi_hid_pdata = { + .sensor_pdata = { + .sensor_type = rmi_sensor_touchpad, + .axis_align.flip_y = true, + .dribble = RMI_REG_STATE_ON, + .palm_detect = RMI_REG_STATE_OFF, + }, +}; + +static const struct rmi_transport_ops hid_rmi_ops = { + .write_block = rmi_hid_write_block, + .read_block = rmi_hid_read_block, + .reset = rmi_hid_reset, +}; + +static void rmi_irq_teardown(void *data) +{ + struct rmi_data *hdata = data; + struct irq_domain *domain = hdata->domain; + + if (!domain) + return; + + irq_dispose_mapping(irq_find_mapping(domain, 0)); + + irq_domain_remove(domain); + hdata->domain = NULL; + hdata->rmi_irq = 0; +} + +static int rmi_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw_irq_num) +{ + irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); + + return 0; +} + +static const struct irq_domain_ops rmi_irq_ops = { + .map = rmi_irq_map, +}; + +static int rmi_setup_irq_domain(struct hid_device *hdev) +{ + struct rmi_data *hdata = hid_get_drvdata(hdev); + int ret; + + hdata->domain = irq_domain_create_linear(hdev->dev.fwnode, 1, + &rmi_irq_ops, hdata); + if (!hdata->domain) + return -ENOMEM; + + ret = devm_add_action_or_reset(&hdev->dev, &rmi_irq_teardown, hdata); + if (ret) + return ret; + + hdata->rmi_irq = irq_create_mapping(hdata->domain, 0); + if (hdata->rmi_irq <= 0) { + hid_err(hdev, "Can't allocate an IRQ\n"); + return hdata->rmi_irq < 0 ? hdata->rmi_irq : -ENXIO; + } + + return 0; +} + +static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct rmi_data *data = NULL; + int ret; + size_t alloc_size; + struct hid_report *input_report; + struct hid_report *output_report; + struct hid_report *feature_report; + + data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + INIT_WORK(&data->reset_work, rmi_reset_work); + data->hdev = hdev; + + hid_set_drvdata(hdev, data); + + hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS; + hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + if (id->driver_data) + data->device_flags = id->driver_data; + + /* + * Check for the RMI specific report ids. If they are misisng + * simply return and let the events be processed by hid-input + */ + if (!rmi_check_valid_report_id(hdev, HID_FEATURE_REPORT, + RMI_SET_RMI_MODE_REPORT_ID, &feature_report)) { + hid_dbg(hdev, "device does not have set mode feature report\n"); + goto start; + } + + if (!rmi_check_valid_report_id(hdev, HID_INPUT_REPORT, + RMI_ATTN_REPORT_ID, &input_report)) { + hid_dbg(hdev, "device does not have attention input report\n"); + goto start; + } + + data->input_report_size = hid_report_len(input_report); + + if (!rmi_check_valid_report_id(hdev, HID_OUTPUT_REPORT, + RMI_WRITE_REPORT_ID, &output_report)) { + hid_dbg(hdev, + "device does not have rmi write output report\n"); + goto start; + } + + data->output_report_size = hid_report_len(output_report); + + data->device_flags |= RMI_DEVICE; + alloc_size = data->output_report_size + data->input_report_size; + + data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL); + if (!data->writeReport) { + hid_err(hdev, "failed to allocate buffer for HID reports\n"); + return -ENOMEM; + } + + data->readReport = data->writeReport + data->output_report_size; + + init_waitqueue_head(&data->wait); + + mutex_init(&data->page_mutex); + + ret = rmi_setup_irq_domain(hdev); + if (ret) { + hid_err(hdev, "failed to allocate IRQ domain\n"); + return ret; + } + + if (data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS) + rmi_hid_pdata.gpio_data.disable = true; + + data->xport.dev = hdev->dev.parent; + data->xport.pdata = rmi_hid_pdata; + data->xport.pdata.irq = data->rmi_irq; + data->xport.proto_name = "hid"; + data->xport.ops = &hid_rmi_ops; + +start: + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + return 0; +} + +static void rmi_remove(struct hid_device *hdev) +{ + struct rmi_data *hdata = hid_get_drvdata(hdev); + + if ((hdata->device_flags & RMI_DEVICE) + && test_bit(RMI_STARTED, &hdata->flags)) { + clear_bit(RMI_STARTED, &hdata->flags); + cancel_work_sync(&hdata->reset_work); + rmi_unregister_transport_device(&hdata->xport); + } + + hid_hw_stop(hdev); +} + +static const struct hid_device_id rmi_id[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, USB_DEVICE_ID_RAZER_BLADE_14), + .driver_data = RMI_DEVICE_HAS_PHYS_BUTTONS }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_COVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_REZEL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5), + .driver_data = RMI_DEVICE_OUTPUT_SET_REPORT }, + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_RMI, HID_ANY_ID, HID_ANY_ID) }, + { } +}; +MODULE_DEVICE_TABLE(hid, rmi_id); + +static struct hid_driver rmi_driver = { + .name = "hid-rmi", + .id_table = rmi_id, + .probe = rmi_probe, + .remove = rmi_remove, + .event = rmi_event, + .raw_event = rmi_raw_event, + .report = rmi_report, + .input_mapping = rmi_input_mapping, + .input_configured = rmi_input_configured, +#ifdef CONFIG_PM + .suspend = rmi_suspend, + .resume = rmi_post_resume, + .reset_resume = rmi_post_resume, +#endif +}; + +module_hid_driver(rmi_driver); + +MODULE_AUTHOR("Andrew Duggan <aduggan@synaptics.com>"); +MODULE_DESCRIPTION("RMI HID driver"); +MODULE_LICENSE("GPL"); |