// SPDX-License-Identifier: GPL-2.0-only
/*
* MCP2221A - Microchip USB to I2C Host Protocol Bridge
*
* Copyright (c) 2020, Rishi Gupta <gupt21@gmail.com>
*
* Datasheet: http://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,
};
/*
* 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;