// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2022-2024, Linaro Ltd * Authors: * Bjorn Andersson * Dmitry Baryshkov */ #include #include #include #include #include #include #include #include #include "ucsi.h" struct yoga_c630_ucsi { struct yoga_c630_ec *ec; struct ucsi *ucsi; struct notifier_block nb; u16 version; }; static int yoga_c630_ucsi_read_version(struct ucsi *ucsi, u16 *version) { struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); *version = uec->version; return 0; } static int yoga_c630_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) { 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; memcpy(cci, buf, sizeof(*cci)); return 0; } static int yoga_c630_ucsi_read_message_in(struct ucsi *ucsi, 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; memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE, min(val_len, YOGA_C630_UCSI_DATA_SIZE)); return 0; } static int yoga_c630_ucsi_async_control(struct ucsi *ucsi, u64 command) { struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); return yoga_c630_ec_ucsi_write(uec->ec, (u8*)&command); } const struct ucsi_operations yoga_c630_ucsi_ops = { .read_version = yoga_c630_ucsi_read_version, .read_cci = yoga_c630_ucsi_read_cci, .read_message_in = yoga_c630_ucsi_read_message_in, .sync_control = ucsi_sync_control_common, .async_control = yoga_c630_ucsi_async_control, }; 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_cci(uec->ucsi, &cci); if (ret) return NOTIFY_DONE; ucsi_notify_common(uec->ucsi, 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; 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); } static const struct auxiliary_device_id yoga_c630_ucsi_id_table[] = { { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_UCSI, }, {} }; MODULE_DEVICE_TABLE(auxiliary, yoga_c630_ucsi_id_table); static struct auxiliary_driver yoga_c630_ucsi_driver = { .name = YOGA_C630_DEV_UCSI, .id_table = yoga_c630_ucsi_id_table, .probe = yoga_c630_ucsi_probe, .remove = yoga_c630_ucsi_remove, }; module_auxiliary_driver(yoga_c630_ucsi_driver); MODULE_DESCRIPTION("Lenovo Yoga C630 UCSI"); MODULE_LICENSE("GPL");