From patchwork Thu Apr 23 21:49:26 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ellen Wang X-Patchwork-Id: 6265481 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 9500B9F389 for ; Thu, 23 Apr 2015 21:49:37 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 81C4520380 for ; Thu, 23 Apr 2015 21:49:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id A37902037C for ; Thu, 23 Apr 2015 21:49:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753187AbbDWVtc (ORCPT ); Thu, 23 Apr 2015 17:49:32 -0400 Received: from mail-pd0-f182.google.com ([209.85.192.182]:35029 "EHLO mail-pd0-f182.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754541AbbDWVtb (ORCPT ); Thu, 23 Apr 2015 17:49:31 -0400 Received: by pdbqd1 with SMTP id qd1so29555306pdb.2 for ; Thu, 23 Apr 2015 14:49:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cumulusnetworks.com; s=google; h=from:to:cc:subject:date:message-id; bh=kM6uuYaWwLybVU2urBwSSdvHfqM2l9yHJCHe2AKGFxU=; b=brH7oIq2RrwJEJfLu1CvrLerlPjWAMiYem8hZSrWnPc98voEkr5hUVDEqTxtOnImn9 hM11kwZS3hPPlUqT86TlceOrCjMttksM2rm3Te7TMPfqrz6cu5f8ZjsSN/QJRrjHQfR7 DbO1+INGgFcOiGGUAEAXYQ/TVGnTJDE+pIap0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=kM6uuYaWwLybVU2urBwSSdvHfqM2l9yHJCHe2AKGFxU=; b=XFsyWCQj45BS/MAitxnw7vJi+Qi99b6VDu8Op/DLBw6Z5+dj/OwDNEl2yigLEnAVB+ i63JlLvDSI66+JR8jQL4tDsLXb3LOnUTcUJREfzbD1CAG325nmiJ5zNO0rzyYn//YI+x L3LyfuPVBA7hizh50zhJLrCQidSR7ZdQrJq1tdjNfqgX1ZbXmH3AlUqdhC2N4US+Qwhu YcgOqxGC3U5BiWiA4KmNduNAFe3gXD6D9pxKB79roA//hfQbnqNE/wCSTHDk9xZeFIlr OTyn14Pggf1qZVZIqLN0XcMuLaIyAoTL4S4Yy1ElOt2I3pvS5eodqkXUpT1TsXob/zls QlZg== X-Gm-Message-State: ALoCoQkjLNVwEvW7Da8vdNZtuY8iXtTTXCjxR9Vmy+uBr9SV9PRk8eM4Syv4f/VQs9YIEJaWN+qT X-Received: by 10.66.138.15 with SMTP id qm15mr9167182pab.52.1429825770856; Thu, 23 Apr 2015 14:49:30 -0700 (PDT) Received: from monster-01.cumulusnetworks.com ([216.129.126.126]) by mx.google.com with ESMTPSA id ml6sm9040567pdb.69.2015.04.23.14.49.30 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 23 Apr 2015 14:49:30 -0700 (PDT) From: Ellen Wang To: dbarksdale@uplogix.com, jkosina@suse.cz, linux-input@vger.kernel.org, linux-i2c@vger.kernel.org Cc: ellen@cumulusnetworks.com Subject: [PATCH v1] HID: support multiple and large i2c transfers in hid-cp2112 Date: Thu, 23 Apr 2015 14:49:26 -0700 Message-Id: <1429825766-19594-1-git-send-email-ellen@cumulusnetworks.com> X-Mailer: git-send-email 1.7.10.4 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID,T_RP_MATCHES_RCVD,UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP cp2112_i2c_xfer() only supports a single i2c_msg and only reads up to 61 bytes. More than one message at a time and longers reads just return errors. This is a serious limitation. For example, the at24 eeprom driver generates paired write and read messages (for eeprom address and data). And the reads can be quite large. The fix consists of a loop to go through all the messages, and a loop around cp2112_read() to pick up all the returned data. For extra credit, it now also supports write-read pairs and implements them as CP2112_DATA_WRITE_READ_REQUEST. Signed-off-by: Ellen Wang --- drivers/hid/hid-cp2112.c | 136 +++++++++++++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 49 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 3318de6..e7e72a4 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -444,11 +444,30 @@ static int cp2112_i2c_write_req(void *buf, u8 slave_address, u8 *data, return data_length + 3; } +static int cp2112_i2c_write_read_req(void *buf, u8 slave_address, + u8 *addr, int addr_length, + int read_length) +{ + struct cp2112_write_read_req_report *report = buf; + + if (read_length < 1 || read_length > 512 || + addr_length > sizeof(report->target_address)) + return -EINVAL; + + report->report = CP2112_DATA_WRITE_READ_REQUEST; + report->slave_address = slave_address << 1; + report->length = cpu_to_be16(read_length); + report->target_address_length = addr_length; + memcpy(report->target_address, addr, addr_length); + return addr_length + 5; +} + static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct cp2112_device *dev = (struct cp2112_device *)adap->algo_data; struct hid_device *hdev = dev->hdev; + struct i2c_msg *m; u8 buf[64]; ssize_t count; unsigned int retries; @@ -456,71 +475,90 @@ static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, hid_dbg(hdev, "I2C %d messages\n", num); - if (num != 1) { - hid_err(hdev, - "Multi-message I2C transactions not supported\n"); - return -EOPNOTSUPP; - } - - if (msgs->flags & I2C_M_RD) - count = cp2112_read_req(buf, msgs->addr, msgs->len); - else - count = cp2112_i2c_write_req(buf, msgs->addr, msgs->buf, - msgs->len); - - if (count < 0) - return count; - ret = hid_hw_power(hdev, PM_HINT_FULLON); if (ret < 0) { hid_err(hdev, "power management error: %d\n", ret); return ret; } - ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT); - if (ret < 0) { - hid_warn(hdev, "Error starting transaction: %d\n", ret); - goto power_normal; - } + for (m = msgs; m < msgs + num; m++) { + /* + * If the top two messages are a write followed by a read, + * then we do them together as CP2112_DATA_WRITE_READ_REQUEST. + * Otherwise, process one message. + */ + + if (m + 1 < msgs + num && m[0].addr == m[1].addr && + !(m[0].flags & I2C_M_RD) && (m[1].flags & I2C_M_RD)) { + hid_dbg(hdev, "I2C msg %zd write-read %#04x wlen %d rlen %d\n", + m - msgs, m[0].addr, m[0].len, m[1].len); + count = cp2112_i2c_write_read_req(buf, m[0].addr, + m[0].buf, m[0].len, m[1].len); + m++; + } else if (m->flags & I2C_M_RD) { + hid_dbg(hdev, "I2C msg %zd read %#04x len %d\n", + m - msgs, m->addr, m->len); + count = cp2112_read_req(buf, m->addr, m->len); + } else { + hid_dbg(hdev, "I2C msg %zd write %#04x len %d\n", + m - msgs, m->addr, m->len); + count = cp2112_i2c_write_req(buf, m->addr, m->buf, + m->len); + } - for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) { - ret = cp2112_xfer_status(dev); - if (-EBUSY == ret) - continue; - if (ret < 0) + if (count < 0) { + ret = count; goto power_normal; - break; - } + } - if (XFER_STATUS_RETRIES <= retries) { - hid_warn(hdev, "Transfer timed out, cancelling.\n"); - buf[0] = CP2112_CANCEL_TRANSFER; - buf[1] = 0x01; + ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT); + if (ret < 0) { + hid_warn(hdev, "Error starting transaction: %d\n", ret); + goto power_normal; + } - ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT); - if (ret < 0) - hid_warn(hdev, "Error cancelling transaction: %d\n", - ret); + for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) { + ret = cp2112_xfer_status(dev); + if (-EBUSY == ret) + continue; + if (ret < 0) + goto power_normal; + break; + } - ret = -ETIMEDOUT; - goto power_normal; - } + if (XFER_STATUS_RETRIES <= retries) { + hid_warn(hdev, "Transfer timed out, cancelling.\n"); + buf[0] = CP2112_CANCEL_TRANSFER; + buf[1] = 0x01; - if (!(msgs->flags & I2C_M_RD)) - goto finish; + ret = cp2112_hid_output(hdev, buf, 2, + HID_OUTPUT_REPORT); + if (ret < 0) + hid_warn(hdev, + "Error cancelling transaction: %d\n", + ret); - ret = cp2112_read(dev, msgs->buf, msgs->len); - if (ret < 0) - goto power_normal; - if (ret != msgs->len) { - hid_warn(hdev, "short read: %d < %d\n", ret, msgs->len); - ret = -EIO; - goto power_normal; + ret = -ETIMEDOUT; + goto power_normal; + } + + if (!(m->flags & I2C_M_RD)) + continue; + + for (count = 0; count < m->len;) { + ret = cp2112_read(dev, m->buf + count, m->len - count); + if (ret < 0) + goto power_normal; + count += ret; + if (count > m->len) { + hid_warn(hdev, "long read: %d > %zd\n", + ret, m->len - count + ret); + } + } } -finish: /* return the number of transferred messages */ - ret = 1; + ret = num; power_normal: hid_hw_power(hdev, PM_HINT_NORMAL);