/* * Copyright (C) 2015-2016 Red Hat * Copyright (C) 2015 Lyude Paul * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by * the Free Software Foundation. */ #include #include #include #include #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; 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; 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); } serio_continue_rx(serio); } 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_register_pt(struct f03_data *f03) { struct serio *serio; serio = kzalloc(sizeof(struct serio), GFP_KERNEL); if (!serio) return -ENOMEM; serio->id.type = SERIO_PS_PSTHRU; serio->write = rmi_f03_pt_write; serio->port_data = f03; strlcpy(serio->name, "Synaptics RMI4 PS/2 pass-through", sizeof(serio->name)); strlcpy(serio->phys, "synaptics-rmi4-pt/serio1", sizeof(serio->phys)); serio->dev.parent = &f03->fn->dev; f03->serio = serio; 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); error = rmi_f03_register_pt(f03); if (error) return error; return 0; } static int rmi_f03_config(struct rmi_function *fn) { fn->rmi_dev->driver->set_irq_bits(fn->rmi_dev, fn->irq_mask); return 0; } static int rmi_f03_attention(struct rmi_function *fn, unsigned long *irq_bits) { 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); u16 data_addr = fn->fd.data_base_addr; 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 0; } 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 + RMI_F03_OB_OFFSET, &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 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 0; } static void rmi_f03_remove(struct rmi_function *fn) { struct f03_data *f03 = dev_get_drvdata(&fn->dev); 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 "); MODULE_DESCRIPTION("RMI F03 module"); MODULE_LICENSE("GPL");