diff mbox series

[v5,5/5] HID: mcp2221: add ADC/DAC support via iio subsystem

Message ID 20220927025050.13316-6-matt.ranostay@konsulko.com
State New
Headers show
Series HID: mcp2221: iio support and device resource management | expand

Commit Message

Matt Ranostay Sept. 27, 2022, 2:50 a.m. UTC
Add support for 3x 10-bit ADC and 1x DAC channels registered via the iio
subsystem.

To prevent breakage and unexpected dependencies this support only is
only built if CONFIG_IIO is enabled, and is only weakly referenced by
'imply IIO' within the respective Kconfig.

Additionally the iio device only gets registered if at least one channel
is enabled in the power-on configuration read from SRAM.

Signed-off-by: Matt Ranostay <matt.ranostay@konsulko.com>
---
 drivers/hid/Kconfig       |   1 +
 drivers/hid/hid-mcp2221.c | 258 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 258 insertions(+), 1 deletion(-)

Comments

kernel test robot Sept. 29, 2022, 1:26 p.m. UTC | #1
Hi Matt,

I love your patch! Perhaps something to improve:

[auto build test WARNING on hid/for-next]
[also build test WARNING on wsa/i2c/for-next linus/master v6.0-rc7]
[cannot apply to jic23-iio/togreg next-20220928]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Matt-Ranostay/HID-mcp2221-iio-support-and-device-resource-management/20220927-115651
base:   https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
config: x86_64-randconfig-a011-20220926
compiler: clang version 14.0.6 (https://github.com/llvm/llvm-project f28c006a5895fc0e329fe15fead81e37457cb1d1)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/f7796d3bd7f2584b00018c4ae5748b52e5c0ebc5
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Matt-Ranostay/HID-mcp2221-iio-support-and-device-resource-management/20220927-115651
        git checkout f7796d3bd7f2584b00018c4ae5748b52e5c0ebc5
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=x86_64 SHELL=/bin/bash drivers/hid/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/hid/hid-mcp2221.c:734:11: warning: unused variable 'tmp' [-Wunused-variable]
           u8 *buf, tmp;
                    ^
   1 warning generated.


vim +/tmp +734 drivers/hid/hid-mcp2221.c

   721	
   722	/*
   723	 * MCP2221 uses interrupt endpoint for input reports. This function
   724	 * is called by HID layer when it receives i/p report from mcp2221,
   725	 * which is actually a response to the previously sent command.
   726	 *
   727	 * MCP2221A firmware specific return codes are parsed and 0 or
   728	 * appropriate negative error code is returned. Delayed response
   729	 * results in timeout error and stray reponses results in -EIO.
   730	 */
   731	static int mcp2221_raw_event(struct hid_device *hdev,
   732					struct hid_report *report, u8 *data, int size)
   733	{
 > 734		u8 *buf, tmp;
   735		struct mcp2221 *mcp = hid_get_drvdata(hdev);
   736	
   737		switch (data[0]) {
   738	
   739		case MCP2221_I2C_WR_DATA:
   740		case MCP2221_I2C_WR_NO_STOP:
   741		case MCP2221_I2C_RD_DATA:
   742		case MCP2221_I2C_RD_RPT_START:
   743			switch (data[1]) {
   744			case MCP2221_SUCCESS:
   745				mcp->status = 0;
   746				break;
   747			default:
   748				mcp->status = mcp_get_i2c_eng_state(mcp, data, 2);
   749			}
   750			complete(&mcp->wait_in_report);
   751			break;
   752	
   753		case MCP2221_I2C_PARAM_OR_STATUS:
   754			switch (data[1]) {
   755			case MCP2221_SUCCESS:
   756				if ((mcp->txbuf[3] == MCP2221_I2C_SET_SPEED) &&
   757					(data[3] != MCP2221_I2C_SET_SPEED)) {
   758					mcp->status = -EAGAIN;
   759					break;
   760				}
   761				if (data[20] & MCP2221_I2C_MASK_ADDR_NACK) {
   762					mcp->status = -ENXIO;
   763					break;
   764				}
   765				mcp->status = mcp_get_i2c_eng_state(mcp, data, 8);
   766	#if IS_REACHABLE(CONFIG_IIO)
   767				memcpy(&mcp->adc_values, &data[50], sizeof(mcp->adc_values));
   768	#endif
   769				break;
   770			default:
   771				mcp->status = -EIO;
   772			}
   773			complete(&mcp->wait_in_report);
   774			break;
   775	
   776		case MCP2221_I2C_GET_DATA:
   777			switch (data[1]) {
   778			case MCP2221_SUCCESS:
   779				if (data[2] == MCP2221_I2C_ADDR_NACK) {
   780					mcp->status = -ENXIO;
   781					break;
   782				}
   783				if (!mcp_get_i2c_eng_state(mcp, data, 2)
   784					&& (data[3] == 0)) {
   785					mcp->status = 0;
   786					break;
   787				}
   788				if (data[3] == 127) {
   789					mcp->status = -EIO;
   790					break;
   791				}
   792				if (data[2] == MCP2221_I2C_READ_COMPL) {
   793					buf = mcp->rxbuf;
   794					memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
   795					mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
   796					mcp->status = 0;
   797					break;
   798				}
   799				mcp->status = -EIO;
   800				break;
   801			default:
   802				mcp->status = -EIO;
   803			}
   804			complete(&mcp->wait_in_report);
   805			break;
   806	
   807		case MCP2221_GPIO_GET:
   808			switch (data[1]) {
   809			case MCP2221_SUCCESS:
   810				if ((data[mcp->gp_idx] == MCP2221_ALT_F_NOT_GPIOV) ||
   811					(data[mcp->gp_idx + 1] == MCP2221_ALT_F_NOT_GPIOD)) {
   812					mcp->status = -ENOENT;
   813				} else {
   814					mcp->status = !!data[mcp->gp_idx];
   815					mcp->gpio_dir = data[mcp->gp_idx + 1];
   816				}
   817				break;
   818			default:
   819				mcp->status = -EAGAIN;
   820			}
   821			complete(&mcp->wait_in_report);
   822			break;
   823	
   824		case MCP2221_GPIO_SET:
   825			switch (data[1]) {
   826			case MCP2221_SUCCESS:
   827				if ((data[mcp->gp_idx] == MCP2221_ALT_F_NOT_GPIOV) ||
   828					(data[mcp->gp_idx - 1] == MCP2221_ALT_F_NOT_GPIOV)) {
   829					mcp->status = -ENOENT;
   830				} else {
   831					mcp->status = 0;
   832				}
   833				break;
   834			default:
   835				mcp->status = -EAGAIN;
   836			}
   837			complete(&mcp->wait_in_report);
   838			break;
   839	
   840		case MCP2221_SET_SRAM_SETTINGS:
   841			switch (data[1]) {
   842			case MCP2221_SUCCESS:
   843				mcp->status = 0;
   844				break;
   845			default:
   846				mcp->status = -EAGAIN;
   847			}
   848			complete(&mcp->wait_in_report);
   849			break;
   850	
   851		case MCP2221_GET_SRAM_SETTINGS:
   852			switch (data[1]) {
   853			case MCP2221_SUCCESS:
   854				memcpy(&mcp->mode, &data[22], 4);
   855	#if IS_REACHABLE(CONFIG_IIO)
   856				mcp->dac_value = data[6] & GENMASK(4, 0);
   857	#endif
   858				mcp->status = 0;
   859				break;
   860			default:
   861				mcp->status = -EAGAIN;
   862			}
   863			complete(&mcp->wait_in_report);
   864			break;
   865	
   866		case MCP2221_READ_FLASH_DATA:
   867			switch (data[1]) {
   868			case MCP2221_SUCCESS:
   869				mcp->status = 0;
   870	
   871				/* Only handles CHIP SETTINGS subpage currently */
   872				if (mcp->txbuf[1] != 0) {
   873					mcp->status = -EIO;
   874					break;
   875				}
   876
diff mbox series

Patch

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 6ce92830b5d1..8ee4a4a852a1 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1299,6 +1299,7 @@  config HID_MCP2221
 	tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
 	depends on USB_HID && I2C
 	depends on GPIOLIB
+	imply IIO
 	help
 	Provides I2C and SMBUS host adapter functionality over USB-HID
 	through MCP2221 device.
diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c
index 3365e21547c6..fdffd12bc3b6 100644
--- a/drivers/hid/hid-mcp2221.c
+++ b/drivers/hid/hid-mcp2221.c
@@ -10,12 +10,14 @@ 
 #include <linux/module.h>
 #include <linux/err.h>
 #include <linux/mutex.h>
+#include <linux/bitfield.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 <linux/iio/iio.h>
 #include "hid-ids.h"
 
 /* Commands codes in a raw output report */
@@ -30,6 +32,9 @@  enum {
 	MCP2221_I2C_CANCEL = 0x10,
 	MCP2221_GPIO_SET = 0x50,
 	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 */
@@ -89,6 +94,7 @@  struct mcp2221 {
 	struct i2c_adapter adapter;
 	struct mutex lock;
 	struct completion wait_in_report;
+	struct delayed_work init_work;
 	u8 *rxbuf;
 	u8 txbuf[64];
 	int rxbuf_idx;
@@ -97,6 +103,18 @@  struct mcp2221 {
 	struct gpio_chip *gc;
 	u8 gp_idx;
 	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;
 };
 
 /*
@@ -713,7 +731,7 @@  static int mcp_get_i2c_eng_state(struct mcp2221 *mcp,
 static int mcp2221_raw_event(struct hid_device *hdev,
 				struct hid_report *report, u8 *data, int size)
 {
-	u8 *buf;
+	u8 *buf, tmp;
 	struct mcp2221 *mcp = hid_get_drvdata(hdev);
 
 	switch (data[0]) {
@@ -745,6 +763,9 @@  static int mcp2221_raw_event(struct hid_device *hdev,
 				break;
 			}
 			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;
 		default:
 			mcp->status = -EIO;
@@ -816,6 +837,66 @@  static int mcp2221_raw_event(struct hid_device *hdev,
 		complete(&mcp->wait_in_report);
 		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)
+			/* 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:
 		mcp->status = -EIO;
 		complete(&mcp->wait_in_report);
@@ -839,6 +920,176 @@  static void mcp2221_remove(struct hid_device *hdev)
 	return;
 }
 
+#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(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,
 					const struct hid_device_id *id)
 {
@@ -920,6 +1171,11 @@  static int mcp2221_probe(struct hid_device *hdev,
 	if (ret)
 		return ret;
 
+#if IS_REACHABLE(CONFIG_IIO)
+	INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
+	schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
+#endif
+
 	return 0;
 }