Commit 9a6f62b5 authored by Jiri Kosina's avatar Jiri Kosina

Merge branch 'for-6.2/mcp2221' into for-linus

- iio support for the MCP2221 HID driver (Matt Ranostay)
parents 3daac75d 3d74c9ec
...@@ -1252,7 +1252,8 @@ config HID_ALPS ...@@ -1252,7 +1252,8 @@ config HID_ALPS
config HID_MCP2221 config HID_MCP2221
tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support" tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
depends on USB_HID && I2C depends on USB_HID && I2C
depends on GPIOLIB imply GPIOLIB
imply IIO
help help
Provides I2C and SMBUS host adapter functionality over USB-HID Provides I2C and SMBUS host adapter functionality over USB-HID
through MCP2221 device. through MCP2221 device.
......
...@@ -10,12 +10,14 @@ ...@@ -10,12 +10,14 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/bitfield.h>
#include <linux/completion.h> #include <linux/completion.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/hid.h> #include <linux/hid.h>
#include <linux/hidraw.h> #include <linux/hidraw.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/gpio/driver.h> #include <linux/gpio/driver.h>
#include <linux/iio/iio.h>
#include "hid-ids.h" #include "hid-ids.h"
/* Commands codes in a raw output report */ /* Commands codes in a raw output report */
...@@ -30,6 +32,9 @@ enum { ...@@ -30,6 +32,9 @@ enum {
MCP2221_I2C_CANCEL = 0x10, MCP2221_I2C_CANCEL = 0x10,
MCP2221_GPIO_SET = 0x50, MCP2221_GPIO_SET = 0x50,
MCP2221_GPIO_GET = 0x51, MCP2221_GPIO_GET = 0x51,
MCP2221_SET_SRAM_SETTINGS = 0x60,
MCP2221_GET_SRAM_SETTINGS = 0x61,
MCP2221_READ_FLASH_DATA = 0xb0,
}; };
/* Response codes in a raw input report */ /* Response codes in a raw input report */
...@@ -89,6 +94,7 @@ struct mcp2221 { ...@@ -89,6 +94,7 @@ struct mcp2221 {
struct i2c_adapter adapter; struct i2c_adapter adapter;
struct mutex lock; struct mutex lock;
struct completion wait_in_report; struct completion wait_in_report;
struct delayed_work init_work;
u8 *rxbuf; u8 *rxbuf;
u8 txbuf[64]; u8 txbuf[64];
int rxbuf_idx; int rxbuf_idx;
...@@ -97,6 +103,18 @@ struct mcp2221 { ...@@ -97,6 +103,18 @@ struct mcp2221 {
struct gpio_chip *gc; struct gpio_chip *gc;
u8 gp_idx; u8 gp_idx;
u8 gpio_dir; u8 gpio_dir;
u8 mode[4];
#if IS_REACHABLE(CONFIG_IIO)
struct iio_chan_spec iio_channels[3];
u16 adc_values[3];
u8 adc_scale;
u8 dac_value;
u16 dac_scale;
#endif
};
struct mcp2221_iio {
struct mcp2221 *mcp;
}; };
/* /*
...@@ -567,6 +585,7 @@ static const struct i2c_algorithm mcp_i2c_algo = { ...@@ -567,6 +585,7 @@ static const struct i2c_algorithm mcp_i2c_algo = {
.functionality = mcp_i2c_func, .functionality = mcp_i2c_func,
}; };
#if IS_REACHABLE(CONFIG_GPIOLIB)
static int mcp_gpio_get(struct gpio_chip *gc, static int mcp_gpio_get(struct gpio_chip *gc,
unsigned int offset) unsigned int offset)
{ {
...@@ -670,6 +689,7 @@ static int mcp_gpio_get_direction(struct gpio_chip *gc, ...@@ -670,6 +689,7 @@ static int mcp_gpio_get_direction(struct gpio_chip *gc,
return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_OUT;
} }
#endif
/* Gives current state of i2c engine inside mcp2221 */ /* Gives current state of i2c engine inside mcp2221 */
static int mcp_get_i2c_eng_state(struct mcp2221 *mcp, static int mcp_get_i2c_eng_state(struct mcp2221 *mcp,
...@@ -745,6 +765,9 @@ static int mcp2221_raw_event(struct hid_device *hdev, ...@@ -745,6 +765,9 @@ static int mcp2221_raw_event(struct hid_device *hdev,
break; break;
} }
mcp->status = mcp_get_i2c_eng_state(mcp, data, 8); mcp->status = mcp_get_i2c_eng_state(mcp, data, 8);
#if IS_REACHABLE(CONFIG_IIO)
memcpy(&mcp->adc_values, &data[50], sizeof(mcp->adc_values));
#endif
break; break;
default: default:
mcp->status = -EIO; mcp->status = -EIO;
...@@ -816,6 +839,69 @@ static int mcp2221_raw_event(struct hid_device *hdev, ...@@ -816,6 +839,69 @@ static int mcp2221_raw_event(struct hid_device *hdev,
complete(&mcp->wait_in_report); complete(&mcp->wait_in_report);
break; break;
case MCP2221_SET_SRAM_SETTINGS:
switch (data[1]) {
case MCP2221_SUCCESS:
mcp->status = 0;
break;
default:
mcp->status = -EAGAIN;
}
complete(&mcp->wait_in_report);
break;
case MCP2221_GET_SRAM_SETTINGS:
switch (data[1]) {
case MCP2221_SUCCESS:
memcpy(&mcp->mode, &data[22], 4);
#if IS_REACHABLE(CONFIG_IIO)
mcp->dac_value = data[6] & GENMASK(4, 0);
#endif
mcp->status = 0;
break;
default:
mcp->status = -EAGAIN;
}
complete(&mcp->wait_in_report);
break;
case MCP2221_READ_FLASH_DATA:
switch (data[1]) {
case MCP2221_SUCCESS:
mcp->status = 0;
/* Only handles CHIP SETTINGS subpage currently */
if (mcp->txbuf[1] != 0) {
mcp->status = -EIO;
break;
}
#if IS_REACHABLE(CONFIG_IIO)
{
u8 tmp;
/* DAC scale value */
tmp = FIELD_GET(GENMASK(7, 6), data[6]);
if ((data[6] & BIT(5)) && tmp)
mcp->dac_scale = tmp + 4;
else
mcp->dac_scale = 5;
/* ADC scale value */
tmp = FIELD_GET(GENMASK(4, 3), data[7]);
if ((data[7] & BIT(2)) && tmp)
mcp->adc_scale = tmp - 1;
else
mcp->adc_scale = 0;
}
#endif
break;
default:
mcp->status = -EAGAIN;
}
complete(&mcp->wait_in_report);
break;
default: default:
mcp->status = -EIO; mcp->status = -EIO;
complete(&mcp->wait_in_report); complete(&mcp->wait_in_report);
...@@ -824,6 +910,190 @@ static int mcp2221_raw_event(struct hid_device *hdev, ...@@ -824,6 +910,190 @@ static int mcp2221_raw_event(struct hid_device *hdev,
return 1; return 1;
} }
/* Device resource managed function for HID unregistration */
static void mcp2221_hid_unregister(void *ptr)
{
struct hid_device *hdev = ptr;
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
/* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */
static void mcp2221_remove(struct hid_device *hdev)
{
}
#if IS_REACHABLE(CONFIG_IIO)
static int mcp2221_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *val,
int *val2, long mask)
{
struct mcp2221_iio *priv = iio_priv(indio_dev);
struct mcp2221 *mcp = priv->mcp;
int ret;
if (mask == IIO_CHAN_INFO_SCALE) {
if (channel->output)
*val = 1 << mcp->dac_scale;
else
*val = 1 << mcp->adc_scale;
return IIO_VAL_INT;
}
mutex_lock(&mcp->lock);
if (channel->output) {
*val = mcp->dac_value;
ret = IIO_VAL_INT;
} else {
/* Read ADC values */
ret = mcp_chk_last_cmd_status(mcp);
if (!ret) {
*val = le16_to_cpu((__force __le16) mcp->adc_values[channel->address]);
if (*val >= BIT(10))
ret = -EINVAL;
else
ret = IIO_VAL_INT;
}
}
mutex_unlock(&mcp->lock);
return ret;
}
static int mcp2221_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct mcp2221_iio *priv = iio_priv(indio_dev);
struct mcp2221 *mcp = priv->mcp;
int ret;
if (val < 0 || val >= BIT(5))
return -EINVAL;
mutex_lock(&mcp->lock);
memset(mcp->txbuf, 0, 12);
mcp->txbuf[0] = MCP2221_SET_SRAM_SETTINGS;
mcp->txbuf[4] = BIT(7) | val;
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 12);
if (!ret)
mcp->dac_value = val;
mutex_unlock(&mcp->lock);
return ret;
}
static const struct iio_info mcp2221_info = {
.read_raw = &mcp2221_read_raw,
.write_raw = &mcp2221_write_raw,
};
static int mcp_iio_channels(struct mcp2221 *mcp)
{
int idx, cnt = 0;
bool dac_created = false;
/* GP0 doesn't have ADC/DAC alternative function */
for (idx = 1; idx < MCP_NGPIO; idx++) {
struct iio_chan_spec *chan = &mcp->iio_channels[cnt];
switch (mcp->mode[idx]) {
case 2:
chan->address = idx - 1;
chan->channel = cnt++;
break;
case 3:
/* GP1 doesn't have DAC alternative function */
if (idx == 1 || dac_created)
continue;
/* DAC1 and DAC2 outputs are connected to the same DAC */
dac_created = true;
chan->output = 1;
cnt++;
break;
default:
continue;
};
chan->type = IIO_VOLTAGE;
chan->indexed = 1;
chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
chan->scan_index = -1;
}
return cnt;
}
static void mcp_init_work(struct work_struct *work)
{
struct iio_dev *indio_dev;
struct mcp2221 *mcp = container_of(work, struct mcp2221, init_work.work);
struct mcp2221_iio *data;
static int retries = 5;
int ret, num_channels;
hid_hw_power(mcp->hdev, PM_HINT_FULLON);
mutex_lock(&mcp->lock);
mcp->txbuf[0] = MCP2221_GET_SRAM_SETTINGS;
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
if (ret == -EAGAIN)
goto reschedule_task;
num_channels = mcp_iio_channels(mcp);
if (!num_channels)
goto unlock;
mcp->txbuf[0] = MCP2221_READ_FLASH_DATA;
mcp->txbuf[1] = 0;
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 2);
if (ret == -EAGAIN)
goto reschedule_task;
indio_dev = devm_iio_device_alloc(&mcp->hdev->dev, sizeof(*data));
if (!indio_dev)
goto unlock;
data = iio_priv(indio_dev);
data->mcp = mcp;
indio_dev->name = "mcp2221";
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &mcp2221_info;
indio_dev->channels = mcp->iio_channels;
indio_dev->num_channels = num_channels;
devm_iio_device_register(&mcp->hdev->dev, indio_dev);
unlock:
mutex_unlock(&mcp->lock);
hid_hw_power(mcp->hdev, PM_HINT_NORMAL);
return;
reschedule_task:
mutex_unlock(&mcp->lock);
hid_hw_power(mcp->hdev, PM_HINT_NORMAL);
if (!retries--)
return;
/* Device is not ready to read SRAM or FLASH data, try again */
schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
}
#endif
static int mcp2221_probe(struct hid_device *hdev, static int mcp2221_probe(struct hid_device *hdev,
const struct hid_device_id *id) const struct hid_device_id *id)
{ {
...@@ -849,7 +1119,8 @@ static int mcp2221_probe(struct hid_device *hdev, ...@@ -849,7 +1119,8 @@ static int mcp2221_probe(struct hid_device *hdev,
ret = hid_hw_open(hdev); ret = hid_hw_open(hdev);
if (ret) { if (ret) {
hid_err(hdev, "can't open device\n"); hid_err(hdev, "can't open device\n");
goto err_hstop; hid_hw_stop(hdev);
return ret;
} }
mutex_init(&mcp->lock); mutex_init(&mcp->lock);
...@@ -857,6 +1128,10 @@ static int mcp2221_probe(struct hid_device *hdev, ...@@ -857,6 +1128,10 @@ static int mcp2221_probe(struct hid_device *hdev,
hid_set_drvdata(hdev, mcp); hid_set_drvdata(hdev, mcp);
mcp->hdev = hdev; mcp->hdev = hdev;
ret = devm_add_action_or_reset(&hdev->dev, mcp2221_hid_unregister, hdev);
if (ret)
return ret;
/* Set I2C bus clock diviser */ /* Set I2C bus clock diviser */
if (i2c_clk_freq > 400) if (i2c_clk_freq > 400)
i2c_clk_freq = 400; i2c_clk_freq = 400;
...@@ -873,19 +1148,18 @@ static int mcp2221_probe(struct hid_device *hdev, ...@@ -873,19 +1148,18 @@ static int mcp2221_probe(struct hid_device *hdev,
"MCP2221 usb-i2c bridge on hidraw%d", "MCP2221 usb-i2c bridge on hidraw%d",
((struct hidraw *)hdev->hidraw)->minor); ((struct hidraw *)hdev->hidraw)->minor);
ret = i2c_add_adapter(&mcp->adapter); ret = devm_i2c_add_adapter(&hdev->dev, &mcp->adapter);
if (ret) { if (ret) {
hid_err(hdev, "can't add usb-i2c adapter: %d\n", ret); hid_err(hdev, "can't add usb-i2c adapter: %d\n", ret);
goto err_i2c; return ret;
} }
i2c_set_adapdata(&mcp->adapter, mcp); i2c_set_adapdata(&mcp->adapter, mcp);
#if IS_REACHABLE(CONFIG_GPIOLIB)
/* Setup GPIO chip */ /* Setup GPIO chip */
mcp->gc = devm_kzalloc(&hdev->dev, sizeof(*mcp->gc), GFP_KERNEL); mcp->gc = devm_kzalloc(&hdev->dev, sizeof(*mcp->gc), GFP_KERNEL);
if (!mcp->gc) { if (!mcp->gc)
ret = -ENOMEM; return -ENOMEM;
goto err_gc;
}
mcp->gc->label = "mcp2221_gpio"; mcp->gc->label = "mcp2221_gpio";
mcp->gc->direction_input = mcp_gpio_direction_input; mcp->gc->direction_input = mcp_gpio_direction_input;
...@@ -900,26 +1174,15 @@ static int mcp2221_probe(struct hid_device *hdev, ...@@ -900,26 +1174,15 @@ static int mcp2221_probe(struct hid_device *hdev,
ret = devm_gpiochip_add_data(&hdev->dev, mcp->gc, mcp); ret = devm_gpiochip_add_data(&hdev->dev, mcp->gc, mcp);
if (ret) if (ret)
goto err_gc; return ret;
#endif
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) #if IS_REACHABLE(CONFIG_IIO)
{ INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
struct mcp2221 *mcp = hid_get_drvdata(hdev); schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
#endif
i2c_del_adapter(&mcp->adapter); return 0;
hid_hw_close(mcp->hdev);
hid_hw_stop(mcp->hdev);
} }
static const struct hid_device_id mcp2221_devices[] = { static const struct hid_device_id mcp2221_devices[] = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment