| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2015-2016 Red Hat | |
| * Copyright (C) 2015 Lyude Paul <thatslyude@gmail.com> | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/slab.h> | |
| #include <linux/serio.h> | |
| #include <linux/notifier.h> | |
| #include "rmi_driver.h" | |
| #define RMI_F03_RX_DATA_OFB 0x01 | |
| #define RMI_F03_OB_SIZE 2 | |
| #define RMI_F03_OB_OFFSET 2 | |
| #define RMI_F03_OB_DATA_OFFSET 1 | |
| #define RMI_F03_OB_FLAG_TIMEOUT BIT(6) | |
| #define RMI_F03_OB_FLAG_PARITY BIT(7) | |
| #define RMI_F03_DEVICE_COUNT 0x07 | |
| #define RMI_F03_BYTES_PER_DEVICE 0x07 | |
| #define RMI_F03_BYTES_PER_DEVICE_SHIFT 4 | |
| #define RMI_F03_QUEUE_LENGTH 0x0F | |
| #define PSMOUSE_OOB_EXTRA_BTNS 0x01 | |
| struct f03_data { | |
| struct rmi_function *fn; | |
| struct serio *serio; | |
| bool serio_registered; | |
| unsigned int overwrite_buttons; | |
| u8 device_count; | |
| u8 rx_queue_length; | |
| }; | |
| int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, | |
| int value) | |
| { | |
| struct f03_data *f03 = dev_get_drvdata(&fn->dev); | |
| unsigned int bit; | |
| if (button < BTN_LEFT || button > BTN_MIDDLE) | |
| return -EINVAL; | |
| bit = BIT(button - BTN_LEFT); | |
| if (value) | |
| f03->overwrite_buttons |= bit; | |
| else | |
| f03->overwrite_buttons &= ~bit; | |
| return 0; | |
| } | |
| void rmi_f03_commit_buttons(struct rmi_function *fn) | |
| { | |
| struct f03_data *f03 = dev_get_drvdata(&fn->dev); | |
| struct serio *serio = f03->serio; | |
| guard(serio_pause_rx)(serio); | |
| if (serio->drv) { | |
| serio->drv->interrupt(serio, PSMOUSE_OOB_EXTRA_BTNS, | |
| SERIO_OOB_DATA); | |
| serio->drv->interrupt(serio, f03->overwrite_buttons, | |
| SERIO_OOB_DATA); | |
| } | |
| } | |
| static int rmi_f03_pt_write(struct serio *id, unsigned char val) | |
| { | |
| struct f03_data *f03 = id->port_data; | |
| int error; | |
| rmi_dbg(RMI_DEBUG_FN, &f03->fn->dev, | |
| "%s: Wrote %.2hhx to PS/2 passthrough address", | |
| __func__, val); | |
| error = rmi_write(f03->fn->rmi_dev, f03->fn->fd.data_base_addr, val); | |
| if (error) { | |
| dev_err(&f03->fn->dev, | |
| "%s: Failed to write to F03 TX register (%d).\n", | |
| __func__, error); | |
| return error; | |
| } | |
| return 0; | |
| } | |
| static int rmi_f03_initialize(struct f03_data *f03) | |
| { | |
| struct rmi_function *fn = f03->fn; | |
| struct device *dev = &fn->dev; | |
| int error; | |
| u8 bytes_per_device; | |
| u8 query1; | |
| u8 query2[RMI_F03_DEVICE_COUNT * RMI_F03_BYTES_PER_DEVICE]; | |
| size_t query2_len; | |
| error = rmi_read(fn->rmi_dev, fn->fd.query_base_addr, &query1); | |
| if (error) { | |
| dev_err(dev, "Failed to read query register (%d).\n", error); | |
| return error; | |
| } | |
| f03->device_count = query1 & RMI_F03_DEVICE_COUNT; | |
| bytes_per_device = (query1 >> RMI_F03_BYTES_PER_DEVICE_SHIFT) & | |
| RMI_F03_BYTES_PER_DEVICE; | |
| query2_len = f03->device_count * bytes_per_device; | |
| /* | |
| * The first generation of image sensors don't have a second part to | |
| * their f03 query, as such we have to set some of these values manually | |
| */ | |
| if (query2_len < 1) { | |
| f03->device_count = 1; | |
| f03->rx_queue_length = 7; | |
| } else { | |
| error = rmi_read_block(fn->rmi_dev, fn->fd.query_base_addr + 1, | |
| query2, query2_len); | |
| if (error) { | |
| dev_err(dev, | |
| "Failed to read second set of query registers (%d).\n", | |
| error); | |
| return error; | |
| } | |
| f03->rx_queue_length = query2[0] & RMI_F03_QUEUE_LENGTH; | |
| } | |
| return 0; | |
| } | |
| static int rmi_f03_pt_open(struct serio *serio) | |
| { | |
| struct f03_data *f03 = serio->port_data; | |
| struct rmi_function *fn = f03->fn; | |
| const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE; | |
| const u16 data_addr = fn->fd.data_base_addr + RMI_F03_OB_OFFSET; | |
| u8 obs[RMI_F03_QUEUE_LENGTH * RMI_F03_OB_SIZE]; | |
| int error; | |
| /* | |
| * Consume any pending data. Some devices like to spam with | |
| * 0xaa 0x00 announcements which may confuse us as we try to | |
| * probe the device. | |
| */ | |
| error = rmi_read_block(fn->rmi_dev, data_addr, &obs, ob_len); | |
| if (!error) | |
| rmi_dbg(RMI_DEBUG_FN, &fn->dev, | |
| "%s: Consumed %*ph (%d) from PS2 guest\n", | |
| __func__, ob_len, obs, ob_len); | |
| return fn->rmi_dev->driver->set_irq_bits(fn->rmi_dev, fn->irq_mask); | |
| } | |
| static void rmi_f03_pt_close(struct serio *serio) | |
| { | |
| struct f03_data *f03 = serio->port_data; | |
| struct rmi_function *fn = f03->fn; | |
| fn->rmi_dev->driver->clear_irq_bits(fn->rmi_dev, fn->irq_mask); | |
| } | |
| static int rmi_f03_register_pt(struct f03_data *f03) | |
| { | |
| struct serio *serio; | |
| serio = kzalloc_obj(struct serio); | |
| if (!serio) | |
| return -ENOMEM; | |
| serio->id.type = SERIO_PS_PSTHRU; | |
| serio->write = rmi_f03_pt_write; | |
| serio->open = rmi_f03_pt_open; | |
| serio->close = rmi_f03_pt_close; | |
| serio->port_data = f03; | |
| strscpy(serio->name, "RMI4 PS/2 pass-through", sizeof(serio->name)); | |
| snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", | |
| dev_name(&f03->fn->dev)); | |
| serio->dev.parent = &f03->fn->dev; | |
| f03->serio = serio; | |
| printk(KERN_INFO "serio: %s port at %s\n", | |
| serio->name, dev_name(&f03->fn->dev)); | |
| serio_register_port(serio); | |
| return 0; | |
| } | |
| static int rmi_f03_probe(struct rmi_function *fn) | |
| { | |
| struct device *dev = &fn->dev; | |
| struct f03_data *f03; | |
| int error; | |
| f03 = devm_kzalloc(dev, sizeof(struct f03_data), GFP_KERNEL); | |
| if (!f03) | |
| return -ENOMEM; | |
| f03->fn = fn; | |
| error = rmi_f03_initialize(f03); | |
| if (error < 0) | |
| return error; | |
| if (f03->device_count != 1) | |
| dev_warn(dev, "found %d devices on PS/2 passthrough", | |
| f03->device_count); | |
| dev_set_drvdata(dev, f03); | |
| return 0; | |
| } | |
| static int rmi_f03_config(struct rmi_function *fn) | |
| { | |
| struct f03_data *f03 = dev_get_drvdata(&fn->dev); | |
| int error; | |
| if (!f03->serio_registered) { | |
| error = rmi_f03_register_pt(f03); | |
| if (error) | |
| return error; | |
| f03->serio_registered = true; | |
| } else { | |
| /* | |
| * We must be re-configuring the sensor, just enable | |
| * interrupts for this function. | |
| */ | |
| fn->rmi_dev->driver->set_irq_bits(fn->rmi_dev, fn->irq_mask); | |
| } | |
| return 0; | |
| } | |
| static irqreturn_t rmi_f03_attention(int irq, void *ctx) | |
| { | |
| struct rmi_function *fn = ctx; | |
| struct rmi_device *rmi_dev = fn->rmi_dev; | |
| struct rmi_driver_data *drvdata = dev_get_drvdata(&rmi_dev->dev); | |
| struct f03_data *f03 = dev_get_drvdata(&fn->dev); | |
| const u16 data_addr = fn->fd.data_base_addr + RMI_F03_OB_OFFSET; | |
| const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE; | |
| u8 obs[RMI_F03_QUEUE_LENGTH * RMI_F03_OB_SIZE]; | |
| u8 ob_status; | |
| u8 ob_data; | |
| unsigned int serio_flags; | |
| int i; | |
| int error; | |
| if (drvdata->attn_data.data) { | |
| /* First grab the data passed by the transport device */ | |
| if (drvdata->attn_data.size < ob_len) { | |
| dev_warn(&fn->dev, "F03 interrupted, but data is missing!\n"); | |
| return IRQ_HANDLED; | |
| } | |
| memcpy(obs, drvdata->attn_data.data, ob_len); | |
| drvdata->attn_data.data += ob_len; | |
| drvdata->attn_data.size -= ob_len; | |
| } else { | |
| /* Grab all of the data registers, and check them for data */ | |
| error = rmi_read_block(fn->rmi_dev, data_addr, &obs, ob_len); | |
| if (error) { | |
| dev_err(&fn->dev, | |
| "%s: Failed to read F03 output buffers: %d\n", | |
| __func__, error); | |
| serio_interrupt(f03->serio, 0, SERIO_TIMEOUT); | |
| return IRQ_RETVAL(error); | |
| } | |
| } | |
| for (i = 0; i < ob_len; i += RMI_F03_OB_SIZE) { | |
| ob_status = obs[i]; | |
| ob_data = obs[i + RMI_F03_OB_DATA_OFFSET]; | |
| serio_flags = 0; | |
| if (!(ob_status & RMI_F03_RX_DATA_OFB)) | |
| continue; | |
| if (ob_status & RMI_F03_OB_FLAG_TIMEOUT) | |
| serio_flags |= SERIO_TIMEOUT; | |
| if (ob_status & RMI_F03_OB_FLAG_PARITY) | |
| serio_flags |= SERIO_PARITY; | |
| rmi_dbg(RMI_DEBUG_FN, &fn->dev, | |
| "%s: Received %.2hhx from PS2 guest T: %c P: %c\n", | |
| __func__, ob_data, | |
| serio_flags & SERIO_TIMEOUT ? 'Y' : 'N', | |
| serio_flags & SERIO_PARITY ? 'Y' : 'N'); | |
| serio_interrupt(f03->serio, ob_data, serio_flags); | |
| } | |
| return IRQ_HANDLED; | |
| } | |
| static void rmi_f03_remove(struct rmi_function *fn) | |
| { | |
| struct f03_data *f03 = dev_get_drvdata(&fn->dev); | |
| if (f03->serio_registered) | |
| serio_unregister_port(f03->serio); | |
| } | |
| struct rmi_function_handler rmi_f03_handler = { | |
| .driver = { | |
| .name = "rmi4_f03", | |
| }, | |
| .func = 0x03, | |
| .probe = rmi_f03_probe, | |
| .config = rmi_f03_config, | |
| .attention = rmi_f03_attention, | |
| .remove = rmi_f03_remove, | |
| }; | |
| MODULE_AUTHOR("Lyude Paul <thatslyude@gmail.com>"); | |
| MODULE_DESCRIPTION("RMI F03 module"); | |
| MODULE_LICENSE("GPL"); |