diff mbox series

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

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

Commit Message

Matt Ranostay Sept. 21, 2022, 6:30 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 | 252 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 252 insertions(+), 1 deletion(-)

Comments

kernel test robot Sept. 22, 2022, 10:23 a.m. UTC | #1
Hi Matt,

I love your patch! Yet something to improve:

[auto build test ERROR on jic23-iio/togreg]
[also build test ERROR on next-20220921]
[cannot apply to hid/for-next wsa/i2c/for-next linus/master v6.0-rc6]
[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/20220921-143207
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
config: hexagon-randconfig-r023-20220921 (https://download.01.org/0day-ci/archive/20220922/202209221851.IOfsmA0z-lkp@intel.com/config)
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 791a7ae1ba3efd6bca96338e10ffde557ba83920)
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/9576b88476cb586e6d9f8ef77969f1acd5e4a241
        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/20220921-143207
        git checkout 9576b88476cb586e6d9f8ef77969f1acd5e4a241
        # 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=hexagon 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 errors (new ones prefixed by >>):

>> drivers/hid/hid-mcp2221.c:879:10: error: no member named 'dac_scale' in 'struct mcp2221'
                                   mcp->dac_scale = tmp + 4;
                                   ~~~  ^
   drivers/hid/hid-mcp2221.c:881:10: error: no member named 'dac_scale' in 'struct mcp2221'
                                   mcp->dac_scale = 5;
                                   ~~~  ^
>> drivers/hid/hid-mcp2221.c:886:10: error: no member named 'adc_scale' in 'struct mcp2221'
                                   mcp->adc_scale = tmp - 1;
                                   ~~~  ^
   drivers/hid/hid-mcp2221.c:888:10: error: no member named 'adc_scale' in 'struct mcp2221'
                                   mcp->adc_scale = 0;
                                   ~~~  ^
   4 errors generated.


vim +879 drivers/hid/hid-mcp2221.c

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

I love your patch! Yet something to improve:

[auto build test ERROR on jic23-iio/togreg]
[also build test ERROR on next-20220921]
[cannot apply to hid/for-next wsa/i2c/for-next linus/master v6.0-rc6]
[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/20220921-143207
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
config: sparc-randconfig-r013-20220921
compiler: sparc-linux-gcc (GCC) 12.1.0
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/9576b88476cb586e6d9f8ef77969f1acd5e4a241
        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/20220921-143207
        git checkout 9576b88476cb586e6d9f8ef77969f1acd5e4a241
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sparc 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 errors (new ones prefixed by >>):

   drivers/hid/hid-mcp2221.c: In function 'mcp2221_raw_event':
>> drivers/hid/hid-mcp2221.c:879:36: error: 'struct mcp2221' has no member named 'dac_scale'
     879 |                                 mcp->dac_scale = tmp + 4;
         |                                    ^~
   drivers/hid/hid-mcp2221.c:881:36: error: 'struct mcp2221' has no member named 'dac_scale'
     881 |                                 mcp->dac_scale = 5;
         |                                    ^~
>> drivers/hid/hid-mcp2221.c:886:36: error: 'struct mcp2221' has no member named 'adc_scale'
     886 |                                 mcp->adc_scale = tmp - 1;
         |                                    ^~
   drivers/hid/hid-mcp2221.c:888:36: error: 'struct mcp2221' has no member named 'adc_scale'
     888 |                                 mcp->adc_scale = 0;
         |                                    ^~


vim +879 drivers/hid/hid-mcp2221.c

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

> 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>
> ---
A few comments inline.
> +
> +	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;
> +			}
> +
> +			/* DAC scale value */
> +			tmp = (data[6] >> 6) & 0x3;

Perhaps use FIELD_GET() and a suitably defined mask?

> +			if ((data[6] & BIT(5)) && tmp)
> +				mcp->dac_scale = tmp + 4;
> +			else
> +				mcp->dac_scale = 5;
> +
> +			/* ADC scale value */
> +			tmp = (data[7] >> 3) & 0x3;
> +			if ((data[7] & BIT(2)) && tmp)
> +				mcp->adc_scale = tmp - 1;
> +			else
> +				mcp->adc_scale = 0;
> +
> +			break;
> +		default:
> +			mcp->status = -EAGAIN;
> +		}
> +		complete(&mcp->wait_in_report);
> +		break;
> +
>

...

> +
> +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;
> +	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);
> +

No blank line between a call and it's error handlers. Better
to visually group them together.

> +	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);

If you aren't going full devm, I'd keep this as an iio_device_alloc() and
release it by hand in remove.

> +
> +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);
> +
> +	/* Device is not ready to read SRAM or FLASH data, try again */
> +	schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
Add a count.  If we end up here lots of times, probably want to give up!

> +}
> +#endif
> +
>  static int mcp2221_probe(struct hid_device *hdev,
>  					const struct hid_device_id *id)
>  {
> @@ -913,6 +1158,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;
>  }
>
diff mbox series

Patch

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 863d1f96ea57..cdae312f4795 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1228,6 +1228,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 7ba63bcd66de..2a7783b607af 100644
--- a/drivers/hid/hid-mcp2221.c
+++ b/drivers/hid/hid-mcp2221.c
@@ -16,6 +16,7 @@ 
 #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 +31,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 +93,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 +102,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 +730,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 +762,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 +836,64 @@  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;
+			}
+
+			/* DAC scale value */
+			tmp = (data[6] >> 6) & 0x3;
+			if ((data[6] & BIT(5)) && tmp)
+				mcp->dac_scale = tmp + 4;
+			else
+				mcp->dac_scale = 5;
+
+			/* ADC scale value */
+			tmp = (data[7] >> 3) & 0x3;
+			if ((data[7] & BIT(2)) && tmp)
+				mcp->adc_scale = tmp - 1;
+			else
+				mcp->adc_scale = 0;
+
+			break;
+		default:
+			mcp->status = -EAGAIN;
+		}
+		complete(&mcp->wait_in_report);
+		break;
+
 	default:
 		mcp->status = -EIO;
 		complete(&mcp->wait_in_report);
@@ -832,6 +910,173 @@  static void mcp2221_hid_remove(void *ptr)
 	hid_hw_stop(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(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;
+	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);
+
+	/* 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)
 {
@@ -913,6 +1158,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;
 }