On Sun, Jul 31, 2022 at 7:47 PM Jonathan Cameron <jic23@xxxxxxxxxx> wrote: > > On Fri, 29 Jul 2022 23:47:23 +0800 > Matt Ranostay <matt.ranostay@xxxxxxxxxxxx> 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. > > Seems ok, but I've not seen this done before, so will rely on others > to feedback on that element. > > Otherwise, various comments inline. > > > > > Additionally the iio device only gets registered if at least one channel > > is enabled in the power-on configuration read from SRAM. > > > > Cc: Rishi Gupta <gupt21@xxxxxxxxx> > > Cc: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> > > Signed-off-by: Matt Ranostay <matt.ranostay@xxxxxxxxxxxx> > > --- > > drivers/hid/Kconfig | 3 +- > > drivers/hid/hid-mcp2221.c | 207 ++++++++++++++++++++++++++++++++++++++ > > 2 files changed, 209 insertions(+), 1 deletion(-) > > > > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig > > index 6ce92830b5d1..eb4f4bb05226 100644 > > --- a/drivers/hid/Kconfig > > +++ b/drivers/hid/Kconfig > > @@ -1298,7 +1298,8 @@ config HID_ALPS > > config HID_MCP2221 > > tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support" > > depends on USB_HID && I2C > > - depends on GPIOLIB > > + select 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 de52e9f7bb8c..ab8ca2a65592 100644 > > --- a/drivers/hid/hid-mcp2221.c > > +++ b/drivers/hid/hid-mcp2221.c > > @@ -16,6 +16,8 @@ > > #include <linux/hidraw.h> > > #include <linux/i2c.h> > > #include <linux/gpio/driver.h> > > +#include <linux/iio/iio.h> > > +#include <linux/iio/sysfs.h> > > I can't immediately see why you need iio/sysfs.h > That's normally only relevant if non standard ABI is in use. > > > #include "hid-ids.h" > > > > /* Commands codes in a raw output report */ > > @@ -30,6 +32,8 @@ enum { > > MCP2221_I2C_CANCEL = 0x10, > > MCP2221_GPIO_SET = 0x50, > > MCP2221_GPIO_GET = 0x51, > > + MCP2221_SET_SRAM_SETTINGS = 0x60, > > + MCP2221_GET_SRAM_SETTINGS = 0x61, > > }; > > > > /* 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,17 @@ 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]; > > + struct iio_dev *indio_dev; > > + u16 adc_values[3]; > > + u8 dac_value; > > +#endif > > +}; > > + > > +struct mcp2221_iio { > > + struct mcp2221 *mcp; > > }; > > > > /* > > @@ -745,6 +761,10 @@ 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) > > + if (mcp->indio_dev) > > + memcpy(&mcp->adc_values, &data[50], 6); > > +#endif > > break; > > default: > > mcp->status = -EIO; > > @@ -816,6 +836,32 @@ 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); > Might be worth converting to more readable mask define and > FIELD_GET() > > > +#endif > > + mcp->status = 0; > > + break; > > + default: > > + mcp->status = -EAGAIN; > > + } > > + complete(&mcp->wait_in_report); > > + break; > > + > > default: > > mcp->status = -EIO; > > complete(&mcp->wait_in_report); > > @@ -824,6 +870,158 @@ static int mcp2221_raw_event(struct hid_device *hdev, > > return 1; > > } > > > > +#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) > > +{ > > + > No blank line here > > + struct mcp2221_iio *priv = iio_priv(indio_dev); > > + struct mcp2221 *mcp = priv->mcp; > > + int ret; > > + > > + mutex_lock(&mcp->lock); > For readability I'd prefer this duplicated in each of the > branches so clearly matched against the unlocks. > > > + > > + if (channel->output) { > > + *val = mcp->dac_value; > > + > > + mutex_unlock(&mcp->lock); > > + } else { > > + // Read ADC values > As below. > > > + ret = mcp_chk_last_cmd_status(mcp); > > + if (ret < 0) { > > + mutex_unlock(&mcp->lock); > > + return ret; > > + } > > + > > + *val = le16_to_cpu(mcp->adc_values[channel->address]); > > + > > + mutex_unlock(&mcp->lock); > > + > > + // Confirm value is within 10-bit range > > + if (*val > GENMASK(9, 0)) > > + return -EINVAL; > > + } > > + > > + return IIO_VAL_INT; > > +} > > + > > +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 > GENMASK(4, 0)) > This is a bit wierd. I'd either expect comparison with a number > rather than a mask, or FIELD_FIT() Personally I'm fine with that or just using >= 31 for instance > > > > + return -EINVAL; > > + > Single blank line is enough. > > + > > + hid_hw_power(mcp->hdev, PM_HINT_FULLON); > > + > > + mutex_lock(&mcp->lock); > > + > > + memset(mcp->txbuf, 0, 12); > > + mcp->txbuf[0] = MCP2221_SET_SRAM_SETTINGS; > > + mcp->txbuf[4] = BIT(7) | val; > > Given GENMASK usage above, FIELD_PREP() would make this > more 'self documenting' both for the val and BIT(7) BIT(7) signals that the field is changed for the transaction so it can update the value. > > > + > > + ret = mcp_send_data_req_status(mcp, mcp->txbuf, 12); > > + > > + hid_hw_power(mcp->hdev, PM_HINT_NORMAL); > > + > > + if (ret) { > > + mutex_unlock(&mcp->lock); > > + return -EINVAL; > > + } > > + > > + mcp->dac_value = val; > > + > > + mutex_unlock(&mcp->lock); > > + > > + return 0; > > +} > > + > > +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 > > Not consistent with comment style in this driver. /* ... */ > > > + 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 > > As above. > > > + 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->scan_index = -1; > > + } > > + > > + return cnt; > > +} > > + > > +static void mcp_init_work(struct work_struct *work) > > +{ > > + struct mcp2221 *mcp = container_of(work, struct mcp2221, init_work.work); > > + struct mcp2221_iio *iio; > > + 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); > > + > > + hid_hw_power(mcp->hdev, PM_HINT_NORMAL); > > + mutex_unlock(&mcp->lock); > > + > > + if (ret) > > + return; > > + > > + num_channels = mcp_iio_channels(mcp); > > + if (!num_channels) > > + return; > > + > > + mcp->indio_dev = devm_iio_device_alloc(&mcp->hdev->dev, sizeof(*iio)); > This can fail. Noted. > > + > > + iio = iio_priv(mcp->indio_dev); > > + iio->mcp = mcp; > > + > > + mcp->indio_dev->name = "mcp2221"; > > + mcp->indio_dev->modes = INDIO_DIRECT_MODE; > > + mcp->indio_dev->info = &mcp2221_info; > > + mcp->indio_dev->channels = mcp->iio_channels; > > + mcp->indio_dev->num_channels = num_channels; > > + > > + iio_device_register(mcp->indio_dev); > As can this. You need to check both. > Noted. > > +} > > +#endif > > + > > static int mcp2221_probe(struct hid_device *hdev, > > const struct hid_device_id *id) > > { > > @@ -902,6 +1100,11 @@ static int mcp2221_probe(struct hid_device *hdev, > > if (ret) > > goto err_gc; > > > > +#if IS_REACHABLE(CONFIG_IIO) > > + INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work); > > + schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(500)); > > +#endif > > + > > return 0; > > > > err_gc: > > @@ -920,6 +1123,10 @@ static void mcp2221_remove(struct hid_device *hdev) > > i2c_del_adapter(&mcp->adapter); > > hid_hw_close(mcp->hdev); > > hid_hw_stop(mcp->hdev); > > +#if IS_REACHABLE(CONFIG_IIO) > > + if (mcp->indio_dev) > > + iio_device_unregister(mcp->indio_dev); > > +#endif > I'd expect remove to be reverse order of probe. Mind you this driver has a fun > mix of devm and non devm which makes it very hard to reason about correctness > and potential race conditions. I would personally advocate preceding this > patch with a cleanup of that side of things (probably mass usage of devm_add_action_or_reset() > and appropriate callbacks). Yeah the mix of devm and non-devm i agree is less than ideal.. - Matt > > Jonathan > > > } > > > > static const struct hid_device_id mcp2221_devices[] = { >