mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 19:00:25 +00:00
![Lars Povlsen](/assets/img/avatar_default.png)
The mcp2221 driver GPIO output handling has has several issues.
* A wrong value is used for the GPIO direction.
* Wrong offsets are calculated for some GPIO set value/set direction
operations, when offset is larger than 0.
This has been fixed by introducing proper manifest constants for the
direction encoding, and using 'offsetof' when calculating GPIO
register offsets.
The updated driver has been tested with the Sparx5 pcb134/pcb135
board, which has the mcp2221 device with several (output) GPIO's.
Fixes: 328de1c519
("HID: mcp2221: add GPIO functionality support")
Reviewed-by: Rishi Gupta <gupt21@gmail.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
941 lines
21 KiB
C
941 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* MCP2221A - Microchip USB to I2C Host Protocol Bridge
|
|
*
|
|
* Copyright (c) 2020, Rishi Gupta <gupt21@gmail.com>
|
|
*
|
|
* Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/20005565B.pdf
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/hid.h>
|
|
#include <linux/hidraw.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include "hid-ids.h"
|
|
|
|
/* Commands codes in a raw output report */
|
|
enum {
|
|
MCP2221_I2C_WR_DATA = 0x90,
|
|
MCP2221_I2C_WR_NO_STOP = 0x94,
|
|
MCP2221_I2C_RD_DATA = 0x91,
|
|
MCP2221_I2C_RD_RPT_START = 0x93,
|
|
MCP2221_I2C_GET_DATA = 0x40,
|
|
MCP2221_I2C_PARAM_OR_STATUS = 0x10,
|
|
MCP2221_I2C_SET_SPEED = 0x20,
|
|
MCP2221_I2C_CANCEL = 0x10,
|
|
MCP2221_GPIO_SET = 0x50,
|
|
MCP2221_GPIO_GET = 0x51,
|
|
};
|
|
|
|
/* Response codes in a raw input report */
|
|
enum {
|
|
MCP2221_SUCCESS = 0x00,
|
|
MCP2221_I2C_ENG_BUSY = 0x01,
|
|
MCP2221_I2C_START_TOUT = 0x12,
|
|
MCP2221_I2C_STOP_TOUT = 0x62,
|
|
MCP2221_I2C_WRADDRL_TOUT = 0x23,
|
|
MCP2221_I2C_WRDATA_TOUT = 0x44,
|
|
MCP2221_I2C_WRADDRL_NACK = 0x25,
|
|
MCP2221_I2C_MASK_ADDR_NACK = 0x40,
|
|
MCP2221_I2C_WRADDRL_SEND = 0x21,
|
|
MCP2221_I2C_ADDR_NACK = 0x25,
|
|
MCP2221_I2C_READ_COMPL = 0x55,
|
|
MCP2221_ALT_F_NOT_GPIOV = 0xEE,
|
|
MCP2221_ALT_F_NOT_GPIOD = 0xEF,
|
|
};
|
|
|
|
/* MCP GPIO direction encoding */
|
|
enum {
|
|
MCP2221_DIR_OUT = 0x00,
|
|
MCP2221_DIR_IN = 0x01,
|
|
};
|
|
|
|
#define MCP_NGPIO 4
|
|
|
|
/* MCP GPIO set command layout */
|
|
struct mcp_set_gpio {
|
|
u8 cmd;
|
|
u8 dummy;
|
|
struct {
|
|
u8 change_value;
|
|
u8 value;
|
|
u8 change_direction;
|
|
u8 direction;
|
|
} gpio[MCP_NGPIO];
|
|
} __packed;
|
|
|
|
/* MCP GPIO get command layout */
|
|
struct mcp_get_gpio {
|
|
u8 cmd;
|
|
u8 dummy;
|
|
struct {
|
|
u8 direction;
|
|
u8 value;
|
|
} gpio[MCP_NGPIO];
|
|
} __packed;
|
|
|
|
/*
|
|
* There is no way to distinguish responses. Therefore next command
|
|
* is sent only after response to previous has been received. Mutex
|
|
* lock is used for this purpose mainly.
|
|
*/
|
|
struct mcp2221 {
|
|
struct hid_device *hdev;
|
|
struct i2c_adapter adapter;
|
|
struct mutex lock;
|
|
struct completion wait_in_report;
|
|
u8 *rxbuf;
|
|
u8 txbuf[64];
|
|
int rxbuf_idx;
|
|
int status;
|
|
u8 cur_i2c_clk_div;
|
|
struct gpio_chip *gc;
|
|
u8 gp_idx;
|
|
u8 gpio_dir;
|
|
};
|
|
|
|
/*
|
|
* Default i2c bus clock frequency 400 kHz. Modify this if you
|
|
* want to set some other frequency (min 50 kHz - max 400 kHz).
|
|
*/
|
|
static uint i2c_clk_freq = 400;
|
|
|
|
/* Synchronously send output report to the device */
|
|
static int mcp_send_report(struct mcp2221 *mcp,
|
|
u8 *out_report, size_t len)
|
|
{
|
|
u8 *buf;
|
|
int ret;
|
|
|
|
buf = kmemdup(out_report, len, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
/* mcp2221 uses interrupt endpoint for out reports */
|
|
ret = hid_hw_output_report(mcp->hdev, buf, len);
|
|
kfree(buf);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Send o/p report to the device and wait for i/p report to be
|
|
* received from the device. If the device does not respond,
|
|
* we timeout.
|
|
*/
|
|
static int mcp_send_data_req_status(struct mcp2221 *mcp,
|
|
u8 *out_report, int len)
|
|
{
|
|
int ret;
|
|
unsigned long t;
|
|
|
|
reinit_completion(&mcp->wait_in_report);
|
|
|
|
ret = mcp_send_report(mcp, out_report, len);
|
|
if (ret)
|
|
return ret;
|
|
|
|
t = wait_for_completion_timeout(&mcp->wait_in_report,
|
|
msecs_to_jiffies(4000));
|
|
if (!t)
|
|
return -ETIMEDOUT;
|
|
|
|
return mcp->status;
|
|
}
|
|
|
|
/* Check pass/fail for actual communication with i2c slave */
|
|
static int mcp_chk_last_cmd_status(struct mcp2221 *mcp)
|
|
{
|
|
memset(mcp->txbuf, 0, 8);
|
|
mcp->txbuf[0] = MCP2221_I2C_PARAM_OR_STATUS;
|
|
|
|
return mcp_send_data_req_status(mcp, mcp->txbuf, 8);
|
|
}
|
|
|
|
/* Cancels last command releasing i2c bus just in case occupied */
|
|
static int mcp_cancel_last_cmd(struct mcp2221 *mcp)
|
|
{
|
|
memset(mcp->txbuf, 0, 8);
|
|
mcp->txbuf[0] = MCP2221_I2C_PARAM_OR_STATUS;
|
|
mcp->txbuf[2] = MCP2221_I2C_CANCEL;
|
|
|
|
return mcp_send_data_req_status(mcp, mcp->txbuf, 8);
|
|
}
|
|
|
|
static int mcp_set_i2c_speed(struct mcp2221 *mcp)
|
|
{
|
|
int ret;
|
|
|
|
memset(mcp->txbuf, 0, 8);
|
|
mcp->txbuf[0] = MCP2221_I2C_PARAM_OR_STATUS;
|
|
mcp->txbuf[3] = MCP2221_I2C_SET_SPEED;
|
|
mcp->txbuf[4] = mcp->cur_i2c_clk_div;
|
|
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 8);
|
|
if (ret) {
|
|
/* Small delay is needed here */
|
|
usleep_range(980, 1000);
|
|
mcp_cancel_last_cmd(mcp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* An output report can contain minimum 1 and maximum 60 user data
|
|
* bytes. If the number of data bytes is more then 60, we send it
|
|
* in chunks of 60 bytes. Last chunk may contain exactly 60 or less
|
|
* bytes. Total number of bytes is informed in very first report to
|
|
* mcp2221, from that point onwards it first collect all the data
|
|
* from host and then send to i2c slave device.
|
|
*/
|
|
static int mcp_i2c_write(struct mcp2221 *mcp,
|
|
struct i2c_msg *msg, int type, u8 last_status)
|
|
{
|
|
int ret, len, idx, sent;
|
|
|
|
idx = 0;
|
|
sent = 0;
|
|
if (msg->len < 60)
|
|
len = msg->len;
|
|
else
|
|
len = 60;
|
|
|
|
do {
|
|
mcp->txbuf[0] = type;
|
|
mcp->txbuf[1] = msg->len & 0xff;
|
|
mcp->txbuf[2] = msg->len >> 8;
|
|
mcp->txbuf[3] = (u8)(msg->addr << 1);
|
|
|
|
memcpy(&mcp->txbuf[4], &msg->buf[idx], len);
|
|
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, len + 4);
|
|
if (ret)
|
|
return ret;
|
|
|
|
usleep_range(980, 1000);
|
|
|
|
if (last_status) {
|
|
ret = mcp_chk_last_cmd_status(mcp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
sent = sent + len;
|
|
if (sent >= msg->len)
|
|
break;
|
|
|
|
idx = idx + len;
|
|
if ((msg->len - sent) < 60)
|
|
len = msg->len - sent;
|
|
else
|
|
len = 60;
|
|
|
|
/*
|
|
* Testing shows delay is needed between successive writes
|
|
* otherwise next write fails on first-try from i2c core.
|
|
* This value is obtained through automated stress testing.
|
|
*/
|
|
usleep_range(980, 1000);
|
|
} while (len > 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Device reads all data (0 - 65535 bytes) from i2c slave device and
|
|
* stores it in device itself. This data is read back from device to
|
|
* host in multiples of 60 bytes using input reports.
|
|
*/
|
|
static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
|
struct i2c_msg *msg, int type, u16 smbus_addr,
|
|
u8 smbus_len, u8 *smbus_buf)
|
|
{
|
|
int ret;
|
|
u16 total_len;
|
|
|
|
mcp->txbuf[0] = type;
|
|
if (msg) {
|
|
mcp->txbuf[1] = msg->len & 0xff;
|
|
mcp->txbuf[2] = msg->len >> 8;
|
|
mcp->txbuf[3] = (u8)(msg->addr << 1);
|
|
total_len = msg->len;
|
|
mcp->rxbuf = msg->buf;
|
|
} else {
|
|
mcp->txbuf[1] = smbus_len;
|
|
mcp->txbuf[2] = 0;
|
|
mcp->txbuf[3] = (u8)(smbus_addr << 1);
|
|
total_len = smbus_len;
|
|
mcp->rxbuf = smbus_buf;
|
|
}
|
|
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mcp->rxbuf_idx = 0;
|
|
|
|
do {
|
|
memset(mcp->txbuf, 0, 4);
|
|
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;
|
|
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = mcp_chk_last_cmd_status(mcp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
usleep_range(980, 1000);
|
|
} while (mcp->rxbuf_idx < total_len);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mcp_i2c_xfer(struct i2c_adapter *adapter,
|
|
struct i2c_msg msgs[], int num)
|
|
{
|
|
int ret;
|
|
struct mcp2221 *mcp = i2c_get_adapdata(adapter);
|
|
|
|
hid_hw_power(mcp->hdev, PM_HINT_FULLON);
|
|
|
|
mutex_lock(&mcp->lock);
|
|
|
|
/* Setting speed before every transaction is required for mcp2221 */
|
|
ret = mcp_set_i2c_speed(mcp);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
if (num == 1) {
|
|
if (msgs->flags & I2C_M_RD) {
|
|
ret = mcp_i2c_smbus_read(mcp, msgs, MCP2221_I2C_RD_DATA,
|
|
0, 0, NULL);
|
|
} else {
|
|
ret = mcp_i2c_write(mcp, msgs, MCP2221_I2C_WR_DATA, 1);
|
|
}
|
|
if (ret)
|
|
goto exit;
|
|
ret = num;
|
|
} else if (num == 2) {
|
|
/* Ex transaction; send reg address and read its contents */
|
|
if (msgs[0].addr == msgs[1].addr &&
|
|
!(msgs[0].flags & I2C_M_RD) &&
|
|
(msgs[1].flags & I2C_M_RD)) {
|
|
|
|
ret = mcp_i2c_write(mcp, &msgs[0],
|
|
MCP2221_I2C_WR_NO_STOP, 0);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = mcp_i2c_smbus_read(mcp, &msgs[1],
|
|
MCP2221_I2C_RD_RPT_START,
|
|
0, 0, NULL);
|
|
if (ret)
|
|
goto exit;
|
|
ret = num;
|
|
} else {
|
|
dev_err(&adapter->dev,
|
|
"unsupported multi-msg i2c transaction\n");
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
} else {
|
|
dev_err(&adapter->dev,
|
|
"unsupported multi-msg i2c transaction\n");
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
|
|
exit:
|
|
hid_hw_power(mcp->hdev, PM_HINT_NORMAL);
|
|
mutex_unlock(&mcp->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mcp_smbus_write(struct mcp2221 *mcp, u16 addr,
|
|
u8 command, u8 *buf, u8 len, int type,
|
|
u8 last_status)
|
|
{
|
|
int data_len, ret;
|
|
|
|
mcp->txbuf[0] = type;
|
|
mcp->txbuf[1] = len + 1; /* 1 is due to command byte itself */
|
|
mcp->txbuf[2] = 0;
|
|
mcp->txbuf[3] = (u8)(addr << 1);
|
|
mcp->txbuf[4] = command;
|
|
|
|
switch (len) {
|
|
case 0:
|
|
data_len = 5;
|
|
break;
|
|
case 1:
|
|
mcp->txbuf[5] = buf[0];
|
|
data_len = 6;
|
|
break;
|
|
case 2:
|
|
mcp->txbuf[5] = buf[0];
|
|
mcp->txbuf[6] = buf[1];
|
|
data_len = 7;
|
|
break;
|
|
default:
|
|
memcpy(&mcp->txbuf[5], buf, len);
|
|
data_len = len + 5;
|
|
}
|
|
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, data_len);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (last_status) {
|
|
usleep_range(980, 1000);
|
|
|
|
ret = mcp_chk_last_cmd_status(mcp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
|
|
unsigned short flags, char read_write,
|
|
u8 command, int size,
|
|
union i2c_smbus_data *data)
|
|
{
|
|
int ret;
|
|
struct mcp2221 *mcp = i2c_get_adapdata(adapter);
|
|
|
|
hid_hw_power(mcp->hdev, PM_HINT_FULLON);
|
|
|
|
mutex_lock(&mcp->lock);
|
|
|
|
ret = mcp_set_i2c_speed(mcp);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
switch (size) {
|
|
|
|
case I2C_SMBUS_QUICK:
|
|
if (read_write == I2C_SMBUS_READ)
|
|
ret = mcp_i2c_smbus_read(mcp, NULL, MCP2221_I2C_RD_DATA,
|
|
addr, 0, &data->byte);
|
|
else
|
|
ret = mcp_smbus_write(mcp, addr, command, NULL,
|
|
0, MCP2221_I2C_WR_DATA, 1);
|
|
break;
|
|
case I2C_SMBUS_BYTE:
|
|
if (read_write == I2C_SMBUS_READ)
|
|
ret = mcp_i2c_smbus_read(mcp, NULL, MCP2221_I2C_RD_DATA,
|
|
addr, 1, &data->byte);
|
|
else
|
|
ret = mcp_smbus_write(mcp, addr, command, NULL,
|
|
0, MCP2221_I2C_WR_DATA, 1);
|
|
break;
|
|
case I2C_SMBUS_BYTE_DATA:
|
|
if (read_write == I2C_SMBUS_READ) {
|
|
ret = mcp_smbus_write(mcp, addr, command, NULL,
|
|
0, MCP2221_I2C_WR_NO_STOP, 0);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = mcp_i2c_smbus_read(mcp, NULL,
|
|
MCP2221_I2C_RD_RPT_START,
|
|
addr, 1, &data->byte);
|
|
} else {
|
|
ret = mcp_smbus_write(mcp, addr, command, &data->byte,
|
|
1, MCP2221_I2C_WR_DATA, 1);
|
|
}
|
|
break;
|
|
case I2C_SMBUS_WORD_DATA:
|
|
if (read_write == I2C_SMBUS_READ) {
|
|
ret = mcp_smbus_write(mcp, addr, command, NULL,
|
|
0, MCP2221_I2C_WR_NO_STOP, 0);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = mcp_i2c_smbus_read(mcp, NULL,
|
|
MCP2221_I2C_RD_RPT_START,
|
|
addr, 2, (u8 *)&data->word);
|
|
} else {
|
|
ret = mcp_smbus_write(mcp, addr, command,
|
|
(u8 *)&data->word, 2,
|
|
MCP2221_I2C_WR_DATA, 1);
|
|
}
|
|
break;
|
|
case I2C_SMBUS_BLOCK_DATA:
|
|
if (read_write == I2C_SMBUS_READ) {
|
|
ret = mcp_smbus_write(mcp, addr, command, NULL,
|
|
0, MCP2221_I2C_WR_NO_STOP, 1);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
mcp->rxbuf_idx = 0;
|
|
mcp->rxbuf = data->block;
|
|
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
|
if (ret)
|
|
goto exit;
|
|
} else {
|
|
if (!data->block[0]) {
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
ret = mcp_smbus_write(mcp, addr, command, data->block,
|
|
data->block[0] + 1,
|
|
MCP2221_I2C_WR_DATA, 1);
|
|
}
|
|
break;
|
|
case I2C_SMBUS_I2C_BLOCK_DATA:
|
|
if (read_write == I2C_SMBUS_READ) {
|
|
ret = mcp_smbus_write(mcp, addr, command, NULL,
|
|
0, MCP2221_I2C_WR_NO_STOP, 1);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
mcp->rxbuf_idx = 0;
|
|
mcp->rxbuf = data->block;
|
|
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
|
if (ret)
|
|
goto exit;
|
|
} else {
|
|
if (!data->block[0]) {
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
ret = mcp_smbus_write(mcp, addr, command,
|
|
&data->block[1], data->block[0],
|
|
MCP2221_I2C_WR_DATA, 1);
|
|
}
|
|
break;
|
|
case I2C_SMBUS_PROC_CALL:
|
|
ret = mcp_smbus_write(mcp, addr, command,
|
|
(u8 *)&data->word,
|
|
2, MCP2221_I2C_WR_NO_STOP, 0);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = mcp_i2c_smbus_read(mcp, NULL,
|
|
MCP2221_I2C_RD_RPT_START,
|
|
addr, 2, (u8 *)&data->word);
|
|
break;
|
|
case I2C_SMBUS_BLOCK_PROC_CALL:
|
|
ret = mcp_smbus_write(mcp, addr, command, data->block,
|
|
data->block[0] + 1,
|
|
MCP2221_I2C_WR_NO_STOP, 0);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = mcp_i2c_smbus_read(mcp, NULL,
|
|
MCP2221_I2C_RD_RPT_START,
|
|
addr, I2C_SMBUS_BLOCK_MAX,
|
|
data->block);
|
|
break;
|
|
default:
|
|
dev_err(&mcp->adapter.dev,
|
|
"unsupported smbus transaction size:%d\n", size);
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
|
|
exit:
|
|
hid_hw_power(mcp->hdev, PM_HINT_NORMAL);
|
|
mutex_unlock(&mcp->lock);
|
|
return ret;
|
|
}
|
|
|
|
static u32 mcp_i2c_func(struct i2c_adapter *adapter)
|
|
{
|
|
return I2C_FUNC_I2C |
|
|
I2C_FUNC_SMBUS_READ_BLOCK_DATA |
|
|
I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
|
|
(I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_PEC);
|
|
}
|
|
|
|
static const struct i2c_algorithm mcp_i2c_algo = {
|
|
.master_xfer = mcp_i2c_xfer,
|
|
.smbus_xfer = mcp_smbus_xfer,
|
|
.functionality = mcp_i2c_func,
|
|
};
|
|
|
|
static int mcp_gpio_get(struct gpio_chip *gc,
|
|
unsigned int offset)
|
|
{
|
|
int ret;
|
|
struct mcp2221 *mcp = gpiochip_get_data(gc);
|
|
|
|
mcp->txbuf[0] = MCP2221_GPIO_GET;
|
|
|
|
mcp->gp_idx = offsetof(struct mcp_get_gpio, gpio[offset].value);
|
|
|
|
mutex_lock(&mcp->lock);
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
|
mutex_unlock(&mcp->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void mcp_gpio_set(struct gpio_chip *gc,
|
|
unsigned int offset, int value)
|
|
{
|
|
struct mcp2221 *mcp = gpiochip_get_data(gc);
|
|
|
|
memset(mcp->txbuf, 0, 18);
|
|
mcp->txbuf[0] = MCP2221_GPIO_SET;
|
|
|
|
mcp->gp_idx = offsetof(struct mcp_set_gpio, gpio[offset].value);
|
|
|
|
mcp->txbuf[mcp->gp_idx - 1] = 1;
|
|
mcp->txbuf[mcp->gp_idx] = !!value;
|
|
|
|
mutex_lock(&mcp->lock);
|
|
mcp_send_data_req_status(mcp, mcp->txbuf, 18);
|
|
mutex_unlock(&mcp->lock);
|
|
}
|
|
|
|
static int mcp_gpio_dir_set(struct mcp2221 *mcp,
|
|
unsigned int offset, u8 val)
|
|
{
|
|
memset(mcp->txbuf, 0, 18);
|
|
mcp->txbuf[0] = MCP2221_GPIO_SET;
|
|
|
|
mcp->gp_idx = offsetof(struct mcp_set_gpio, gpio[offset].direction);
|
|
|
|
mcp->txbuf[mcp->gp_idx - 1] = 1;
|
|
mcp->txbuf[mcp->gp_idx] = val;
|
|
|
|
return mcp_send_data_req_status(mcp, mcp->txbuf, 18);
|
|
}
|
|
|
|
static int mcp_gpio_direction_input(struct gpio_chip *gc,
|
|
unsigned int offset)
|
|
{
|
|
int ret;
|
|
struct mcp2221 *mcp = gpiochip_get_data(gc);
|
|
|
|
mutex_lock(&mcp->lock);
|
|
ret = mcp_gpio_dir_set(mcp, offset, MCP2221_DIR_IN);
|
|
mutex_unlock(&mcp->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mcp_gpio_direction_output(struct gpio_chip *gc,
|
|
unsigned int offset, int value)
|
|
{
|
|
int ret;
|
|
struct mcp2221 *mcp = gpiochip_get_data(gc);
|
|
|
|
mutex_lock(&mcp->lock);
|
|
ret = mcp_gpio_dir_set(mcp, offset, MCP2221_DIR_OUT);
|
|
mutex_unlock(&mcp->lock);
|
|
|
|
/* Can't configure as output, bailout early */
|
|
if (ret)
|
|
return ret;
|
|
|
|
mcp_gpio_set(gc, offset, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcp_gpio_get_direction(struct gpio_chip *gc,
|
|
unsigned int offset)
|
|
{
|
|
int ret;
|
|
struct mcp2221 *mcp = gpiochip_get_data(gc);
|
|
|
|
mcp->txbuf[0] = MCP2221_GPIO_GET;
|
|
|
|
mcp->gp_idx = offsetof(struct mcp_get_gpio, gpio[offset].direction);
|
|
|
|
mutex_lock(&mcp->lock);
|
|
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
|
mutex_unlock(&mcp->lock);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (mcp->gpio_dir == MCP2221_DIR_IN)
|
|
return GPIO_LINE_DIRECTION_IN;
|
|
|
|
return GPIO_LINE_DIRECTION_OUT;
|
|
}
|
|
|
|
/* Gives current state of i2c engine inside mcp2221 */
|
|
static int mcp_get_i2c_eng_state(struct mcp2221 *mcp,
|
|
u8 *data, u8 idx)
|
|
{
|
|
int ret;
|
|
|
|
switch (data[idx]) {
|
|
case MCP2221_I2C_WRADDRL_NACK:
|
|
case MCP2221_I2C_WRADDRL_SEND:
|
|
ret = -ENXIO;
|
|
break;
|
|
case MCP2221_I2C_START_TOUT:
|
|
case MCP2221_I2C_STOP_TOUT:
|
|
case MCP2221_I2C_WRADDRL_TOUT:
|
|
case MCP2221_I2C_WRDATA_TOUT:
|
|
ret = -ETIMEDOUT;
|
|
break;
|
|
case MCP2221_I2C_ENG_BUSY:
|
|
ret = -EAGAIN;
|
|
break;
|
|
case MCP2221_SUCCESS:
|
|
ret = 0x00;
|
|
break;
|
|
default:
|
|
ret = -EIO;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* MCP2221 uses interrupt endpoint for input reports. This function
|
|
* is called by HID layer when it receives i/p report from mcp2221,
|
|
* which is actually a response to the previously sent command.
|
|
*
|
|
* MCP2221A firmware specific return codes are parsed and 0 or
|
|
* appropriate negative error code is returned. Delayed response
|
|
* results in timeout error and stray reponses results in -EIO.
|
|
*/
|
|
static int mcp2221_raw_event(struct hid_device *hdev,
|
|
struct hid_report *report, u8 *data, int size)
|
|
{
|
|
u8 *buf;
|
|
struct mcp2221 *mcp = hid_get_drvdata(hdev);
|
|
|
|
switch (data[0]) {
|
|
|
|
case MCP2221_I2C_WR_DATA:
|
|
case MCP2221_I2C_WR_NO_STOP:
|
|
case MCP2221_I2C_RD_DATA:
|
|
case MCP2221_I2C_RD_RPT_START:
|
|
switch (data[1]) {
|
|
case MCP2221_SUCCESS:
|
|
mcp->status = 0;
|
|
break;
|
|
default:
|
|
mcp->status = mcp_get_i2c_eng_state(mcp, data, 2);
|
|
}
|
|
complete(&mcp->wait_in_report);
|
|
break;
|
|
|
|
case MCP2221_I2C_PARAM_OR_STATUS:
|
|
switch (data[1]) {
|
|
case MCP2221_SUCCESS:
|
|
if ((mcp->txbuf[3] == MCP2221_I2C_SET_SPEED) &&
|
|
(data[3] != MCP2221_I2C_SET_SPEED)) {
|
|
mcp->status = -EAGAIN;
|
|
break;
|
|
}
|
|
if (data[20] & MCP2221_I2C_MASK_ADDR_NACK) {
|
|
mcp->status = -ENXIO;
|
|
break;
|
|
}
|
|
mcp->status = mcp_get_i2c_eng_state(mcp, data, 8);
|
|
break;
|
|
default:
|
|
mcp->status = -EIO;
|
|
}
|
|
complete(&mcp->wait_in_report);
|
|
break;
|
|
|
|
case MCP2221_I2C_GET_DATA:
|
|
switch (data[1]) {
|
|
case MCP2221_SUCCESS:
|
|
if (data[2] == MCP2221_I2C_ADDR_NACK) {
|
|
mcp->status = -ENXIO;
|
|
break;
|
|
}
|
|
if (!mcp_get_i2c_eng_state(mcp, data, 2)
|
|
&& (data[3] == 0)) {
|
|
mcp->status = 0;
|
|
break;
|
|
}
|
|
if (data[3] == 127) {
|
|
mcp->status = -EIO;
|
|
break;
|
|
}
|
|
if (data[2] == MCP2221_I2C_READ_COMPL) {
|
|
buf = mcp->rxbuf;
|
|
memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
|
|
mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
|
|
mcp->status = 0;
|
|
break;
|
|
}
|
|
mcp->status = -EIO;
|
|
break;
|
|
default:
|
|
mcp->status = -EIO;
|
|
}
|
|
complete(&mcp->wait_in_report);
|
|
break;
|
|
|
|
case MCP2221_GPIO_GET:
|
|
switch (data[1]) {
|
|
case MCP2221_SUCCESS:
|
|
if ((data[mcp->gp_idx] == MCP2221_ALT_F_NOT_GPIOV) ||
|
|
(data[mcp->gp_idx + 1] == MCP2221_ALT_F_NOT_GPIOD)) {
|
|
mcp->status = -ENOENT;
|
|
} else {
|
|
mcp->status = !!data[mcp->gp_idx];
|
|
mcp->gpio_dir = data[mcp->gp_idx + 1];
|
|
}
|
|
break;
|
|
default:
|
|
mcp->status = -EAGAIN;
|
|
}
|
|
complete(&mcp->wait_in_report);
|
|
break;
|
|
|
|
case MCP2221_GPIO_SET:
|
|
switch (data[1]) {
|
|
case MCP2221_SUCCESS:
|
|
if ((data[mcp->gp_idx] == MCP2221_ALT_F_NOT_GPIOV) ||
|
|
(data[mcp->gp_idx - 1] == MCP2221_ALT_F_NOT_GPIOV)) {
|
|
mcp->status = -ENOENT;
|
|
} else {
|
|
mcp->status = 0;
|
|
}
|
|
break;
|
|
default:
|
|
mcp->status = -EAGAIN;
|
|
}
|
|
complete(&mcp->wait_in_report);
|
|
break;
|
|
|
|
default:
|
|
mcp->status = -EIO;
|
|
complete(&mcp->wait_in_report);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int mcp2221_probe(struct hid_device *hdev,
|
|
const struct hid_device_id *id)
|
|
{
|
|
int ret;
|
|
struct mcp2221 *mcp;
|
|
|
|
mcp = devm_kzalloc(&hdev->dev, sizeof(*mcp), GFP_KERNEL);
|
|
if (!mcp)
|
|
return -ENOMEM;
|
|
|
|
ret = hid_parse(hdev);
|
|
if (ret) {
|
|
hid_err(hdev, "can't parse reports\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
|
|
if (ret) {
|
|
hid_err(hdev, "can't start hardware\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = hid_hw_open(hdev);
|
|
if (ret) {
|
|
hid_err(hdev, "can't open device\n");
|
|
goto err_hstop;
|
|
}
|
|
|
|
mutex_init(&mcp->lock);
|
|
init_completion(&mcp->wait_in_report);
|
|
hid_set_drvdata(hdev, mcp);
|
|
mcp->hdev = hdev;
|
|
|
|
/* Set I2C bus clock diviser */
|
|
if (i2c_clk_freq > 400)
|
|
i2c_clk_freq = 400;
|
|
if (i2c_clk_freq < 50)
|
|
i2c_clk_freq = 50;
|
|
mcp->cur_i2c_clk_div = (12000000 / (i2c_clk_freq * 1000)) - 3;
|
|
|
|
mcp->adapter.owner = THIS_MODULE;
|
|
mcp->adapter.class = I2C_CLASS_HWMON;
|
|
mcp->adapter.algo = &mcp_i2c_algo;
|
|
mcp->adapter.retries = 1;
|
|
mcp->adapter.dev.parent = &hdev->dev;
|
|
snprintf(mcp->adapter.name, sizeof(mcp->adapter.name),
|
|
"MCP2221 usb-i2c bridge on hidraw%d",
|
|
((struct hidraw *)hdev->hidraw)->minor);
|
|
|
|
ret = i2c_add_adapter(&mcp->adapter);
|
|
if (ret) {
|
|
hid_err(hdev, "can't add usb-i2c adapter: %d\n", ret);
|
|
goto err_i2c;
|
|
}
|
|
i2c_set_adapdata(&mcp->adapter, mcp);
|
|
|
|
/* Setup GPIO chip */
|
|
mcp->gc = devm_kzalloc(&hdev->dev, sizeof(*mcp->gc), GFP_KERNEL);
|
|
if (!mcp->gc) {
|
|
ret = -ENOMEM;
|
|
goto err_gc;
|
|
}
|
|
|
|
mcp->gc->label = "mcp2221_gpio";
|
|
mcp->gc->direction_input = mcp_gpio_direction_input;
|
|
mcp->gc->direction_output = mcp_gpio_direction_output;
|
|
mcp->gc->get_direction = mcp_gpio_get_direction;
|
|
mcp->gc->set = mcp_gpio_set;
|
|
mcp->gc->get = mcp_gpio_get;
|
|
mcp->gc->ngpio = MCP_NGPIO;
|
|
mcp->gc->base = -1;
|
|
mcp->gc->can_sleep = 1;
|
|
mcp->gc->parent = &hdev->dev;
|
|
|
|
ret = devm_gpiochip_add_data(&hdev->dev, mcp->gc, mcp);
|
|
if (ret)
|
|
goto err_gc;
|
|
|
|
return 0;
|
|
|
|
err_gc:
|
|
i2c_del_adapter(&mcp->adapter);
|
|
err_i2c:
|
|
hid_hw_close(mcp->hdev);
|
|
err_hstop:
|
|
hid_hw_stop(mcp->hdev);
|
|
return ret;
|
|
}
|
|
|
|
static void mcp2221_remove(struct hid_device *hdev)
|
|
{
|
|
struct mcp2221 *mcp = hid_get_drvdata(hdev);
|
|
|
|
i2c_del_adapter(&mcp->adapter);
|
|
hid_hw_close(mcp->hdev);
|
|
hid_hw_stop(mcp->hdev);
|
|
}
|
|
|
|
static const struct hid_device_id mcp2221_devices[] = {
|
|
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2221) },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(hid, mcp2221_devices);
|
|
|
|
static struct hid_driver mcp2221_driver = {
|
|
.name = "mcp2221",
|
|
.id_table = mcp2221_devices,
|
|
.probe = mcp2221_probe,
|
|
.remove = mcp2221_remove,
|
|
.raw_event = mcp2221_raw_event,
|
|
};
|
|
|
|
/* Register with HID core */
|
|
module_hid_driver(mcp2221_driver);
|
|
|
|
MODULE_AUTHOR("Rishi Gupta <gupt21@gmail.com>");
|
|
MODULE_DESCRIPTION("MCP2221 Microchip HID USB to I2C master bridge");
|
|
MODULE_LICENSE("GPL v2");
|