]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/hid/hid-cp2112.c
Merge tag 'arc-v3.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vgupta/arc
[karo-tx-linux.git] / drivers / hid / hid-cp2112.c
index 56be85a9a77c358d84a3aad0007f127d4992fe6d..a822db5a8338090125688d286ddd8fba0cbbd77d 100644 (file)
@@ -240,8 +240,6 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
        u8 buf[5];
        int ret;
 
-       cp2112_gpio_set(chip, offset, value);
-
        ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
                                       sizeof(buf), HID_FEATURE_REPORT,
                                       HID_REQ_GET_REPORT);
@@ -260,6 +258,12 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
                return ret;
        }
 
+       /*
+        * Set gpio value when output direction is already set,
+        * as specified in AN495, Rev. 0.2, cpt. 4.4
+        */
+       cp2112_gpio_set(chip, offset, value);
+
        return 0;
 }
 
@@ -425,6 +429,105 @@ static int cp2112_write_req(void *buf, u8 slave_address, u8 command, u8 *data,
        return data_length + 4;
 }
 
+static int cp2112_i2c_write_req(void *buf, u8 slave_address, u8 *data,
+                               u8 data_length)
+{
+       struct cp2112_write_req_report *report = buf;
+
+       if (data_length > sizeof(report->data))
+               return -EINVAL;
+
+       report->report = CP2112_DATA_WRITE_REQUEST;
+       report->slave_address = slave_address << 1;
+       report->length = data_length;
+       memcpy(report->data, data, data_length);
+       return data_length + 3;
+}
+
+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;
+       u8 buf[64];
+       ssize_t count;
+       unsigned int retries;
+       int ret;
+
+       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 (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) {
+               ret = cp2112_xfer_status(dev);
+               if (-EBUSY == ret)
+                       continue;
+               if (ret < 0)
+                       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, 2, HID_OUTPUT_REPORT);
+               if (ret < 0)
+                       hid_warn(hdev, "Error cancelling transaction: %d\n",
+                                ret);
+
+               ret = -ETIMEDOUT;
+               goto power_normal;
+       }
+
+       if (!(msgs->flags & I2C_M_RD))
+               goto finish;
+
+       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;
+       }
+
+finish:
+       /* return the number of transferred messages */
+       ret = 1;
+
+power_normal:
+       hid_hw_power(hdev, PM_HINT_NORMAL);
+       hid_dbg(hdev, "I2C transfer finished: %d\n", ret);
+       return ret;
+}
+
 static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
                       unsigned short flags, char read_write, u8 command,
                       int size, union i2c_smbus_data *data)
@@ -591,7 +694,8 @@ power_normal:
 
 static u32 cp2112_functionality(struct i2c_adapter *adap)
 {
-       return I2C_FUNC_SMBUS_BYTE |
+       return I2C_FUNC_I2C |
+               I2C_FUNC_SMBUS_BYTE |
                I2C_FUNC_SMBUS_BYTE_DATA |
                I2C_FUNC_SMBUS_WORD_DATA |
                I2C_FUNC_SMBUS_BLOCK_DATA |
@@ -601,6 +705,7 @@ static u32 cp2112_functionality(struct i2c_adapter *adap)
 }
 
 static const struct i2c_algorithm smbus_algorithm = {
+       .master_xfer    = cp2112_i2c_xfer,
        .smbus_xfer     = cp2112_xfer,
        .functionality  = cp2112_functionality,
 };