On Wed, 12 Jun 2024, Dmitry Baryshkov wrote: > The Lenovo Yoga C630 WOS laptop provides implements UCSI interface in > the onboard EC. Add glue driver to interface the platform's UCSI > implementation. > > Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@xxxxxxxxxx> > Reviewed-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx> > --- > +static int yoga_c630_ucsi_read(struct ucsi *ucsi, unsigned int offset, > + void *val, size_t val_len) > +{ > + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); > + u8 buf[YOGA_C630_UCSI_READ_SIZE]; > + int ret; > + > + ret = yoga_c630_ec_ucsi_read(uec->ec, buf); > + if (ret) > + return ret; > + > + if (offset == UCSI_VERSION) { > + memcpy(val, &uec->version, min(val_len, sizeof(uec->version))); > + return 0; > + } > + > + if (offset == UCSI_CCI) > + memcpy(val, buf, min(val_len, YOGA_C630_UCSI_CCI_SIZE)); > + else if (offset == UCSI_MESSAGE_IN) > + memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE, > + min(val_len, YOGA_C630_UCSI_DATA_SIZE)); > + else > + return -EINVAL; > + > + return 0; Hmm, the inconsistency when to do return 0 is a bit odd. Also, using switch (offset) would probably be better here anyway to replace all the ifs. > +} > + > +static int yoga_c630_ucsi_async_write(struct ucsi *ucsi, unsigned int offset, > + const void *val, size_t val_len) > +{ > + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); > + > + if (offset != UCSI_CONTROL || > + val_len != YOGA_C630_UCSI_WRITE_SIZE) > + return -EINVAL; > + > + return yoga_c630_ec_ucsi_write(uec->ec, val); > +} > + > +static int yoga_c630_ucsi_sync_write(struct ucsi *ucsi, unsigned int offset, > + const void *val, size_t val_len) > +{ > + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); > + bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI; > + int ret; > + > + if (ack) > + set_bit(UCSI_C630_ACK_PENDING, &uec->flags); > + else > + set_bit(UCSI_C630_COMMAND_PENDING, &uec->flags); > + > + reinit_completion(&uec->complete); > + > + ret = yoga_c630_ucsi_async_write(ucsi, offset, val, val_len); > + if (ret) > + goto out_clear_bit; > + > + if (!wait_for_completion_timeout(&uec->complete, 5 * HZ)) > + ret = -ETIMEDOUT; > + > +out_clear_bit: > + if (ack) > + clear_bit(UCSI_C630_ACK_PENDING, &uec->flags); > + else > + clear_bit(UCSI_C630_COMMAND_PENDING, &uec->flags); > + > + return ret; > +} > + > +const struct ucsi_operations yoga_c630_ucsi_ops = { > + .read = yoga_c630_ucsi_read, > + .sync_write = yoga_c630_ucsi_sync_write, > + .async_write = yoga_c630_ucsi_async_write, > +}; > + > +static void yoga_c630_ucsi_notify_ucsi(struct yoga_c630_ucsi *uec, u32 cci) > +{ > + if (UCSI_CCI_CONNECTOR(cci)) > + ucsi_connector_change(uec->ucsi, UCSI_CCI_CONNECTOR(cci)); > + > + if (cci & UCSI_CCI_ACK_COMPLETE && > + test_bit(UCSI_C630_ACK_PENDING, &uec->flags)) > + complete(&uec->complete); > + > + if (cci & UCSI_CCI_COMMAND_COMPLETE && > + test_bit(UCSI_C630_COMMAND_PENDING, &uec->flags)) > + complete(&uec->complete); Is this racy? Can another command start after an ACK in between these two ifs and complete() is called prematurely for the new command? (Or will different value in cci protect against that?) > +} > + > +static int yoga_c630_ucsi_notify(struct notifier_block *nb, > + unsigned long action, void *data) > +{ > + struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb); > + u32 cci; > + int ret; > + > + switch (action) { > + case LENOVO_EC_EVENT_USB: > + case LENOVO_EC_EVENT_HPD: > + ucsi_connector_change(uec->ucsi, 1); > + return NOTIFY_OK; > + > + case LENOVO_EC_EVENT_UCSI: > + ret = uec->ucsi->ops->read(uec->ucsi, UCSI_CCI, &cci, sizeof(cci)); > + if (ret) > + return NOTIFY_DONE; > + > + yoga_c630_ucsi_notify_ucsi(uec, cci); > + > + return NOTIFY_OK; > + > + default: > + return NOTIFY_DONE; > + } > +} > + > +static int yoga_c630_ucsi_probe(struct auxiliary_device *adev, > + const struct auxiliary_device_id *id) > +{ > + struct yoga_c630_ec *ec = adev->dev.platform_data; > + struct yoga_c630_ucsi *uec; > + int ret; > + > + uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL); > + if (!uec) > + return -ENOMEM; > + > + uec->ec = ec; > + init_completion(&uec->complete); > + uec->nb.notifier_call = yoga_c630_ucsi_notify; > + > + uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops); > + if (IS_ERR(uec->ucsi)) > + return PTR_ERR(uec->ucsi); > + > + ucsi_set_drvdata(uec->ucsi, uec); > + > + uec->version = yoga_c630_ec_ucsi_get_version(uec->ec); > + > + auxiliary_set_drvdata(adev, uec); > + > + ret = yoga_c630_ec_register_notify(ec, &uec->nb); > + if (ret) > + return ret; > + > + return ucsi_register(uec->ucsi); > +} > + > +static void yoga_c630_ucsi_remove(struct auxiliary_device *adev) > +{ > + struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev); > + > + yoga_c630_ec_unregister_notify(uec->ec, &uec->nb); > + ucsi_unregister(uec->ucsi); Usually, the remove should tear down in reverse order than the probe side. Is the divergence from that here intentional? -- i.