Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

usb: typec: ucsi: Implement ChromeOS UCSI driver

Implementation of a UCSI transport driver for ChromeOS.
This driver will be loaded if the ChromeOS EC implements a PPM.

Signed-off-by: Pavan Holla <pholla@chromium.org>
Co-developed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
Co-developed-by: Łukasz Bartosik <ukaszb@chromium.org>
Signed-off-by: Łukasz Bartosik <ukaszb@chromium.org>
Link: https://lore.kernel.org/r/20241231131047.1757434-3-ukaszb@chromium.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Pavan Holla and committed by
Greg Kroah-Hartman
f1a22417 401d07d5

+358
+7
MAINTAINERS
··· 5457 5457 S: Maintained 5458 5458 F: drivers/watchdog/cros_ec_wdt.c 5459 5459 5460 + CHROMEOS UCSI DRIVER 5461 + M: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> 5462 + M: Łukasz Bartosik <ukaszb@chromium.org> 5463 + L: chrome-platform@lists.linux.dev 5464 + S: Maintained 5465 + F: drivers/usb/typec/ucsi/cros_ec_ucsi.c 5466 + 5460 5467 CHRONTEL CH7322 CEC DRIVER 5461 5468 M: Joe Tessler <jrt@google.com> 5462 5469 L: linux-media@vger.kernel.org
+13
drivers/usb/typec/ucsi/Kconfig
··· 69 69 To compile the driver as a module, choose M here: the module will be 70 70 called ucsi_glink. 71 71 72 + config CROS_EC_UCSI 73 + tristate "UCSI Driver for ChromeOS EC" 74 + depends on MFD_CROS_EC_DEV 75 + depends on CROS_USBPD_NOTIFY 76 + depends on !EXTCON_TCSS_CROS_EC 77 + default MFD_CROS_EC_DEV 78 + help 79 + This driver enables UCSI support for a ChromeOS EC. The EC is 80 + expected to implement a PPM. 81 + 82 + To compile the driver as a module, choose M here: the module 83 + will be called cros_ec_ucsi. 84 + 72 85 config UCSI_LENOVO_YOGA_C630 73 86 tristate "UCSI Interface Driver for Lenovo Yoga C630" 74 87 depends on EC_LENOVO_YOGA_C630
+1
drivers/usb/typec/ucsi/Makefile
··· 21 21 obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o 22 22 obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o 23 23 obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o 24 + obj-$(CONFIG_CROS_EC_UCSI) += cros_ec_ucsi.o 24 25 obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
+337
drivers/usb/typec/ucsi/cros_ec_ucsi.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * UCSI driver for ChromeOS EC 4 + * 5 + * Copyright 2024 Google LLC. 6 + */ 7 + 8 + #include <linux/container_of.h> 9 + #include <linux/dev_printk.h> 10 + #include <linux/jiffies.h> 11 + #include <linux/mod_devicetable.h> 12 + #include <linux/module.h> 13 + #include <linux/platform_data/cros_ec_commands.h> 14 + #include <linux/platform_data/cros_usbpd_notify.h> 15 + #include <linux/platform_data/cros_ec_proto.h> 16 + #include <linux/platform_device.h> 17 + #include <linux/slab.h> 18 + #include <linux/wait.h> 19 + 20 + #include "ucsi.h" 21 + 22 + /* 23 + * Maximum size in bytes of a UCSI message between AP and EC 24 + */ 25 + #define MAX_EC_DATA_SIZE 256 26 + 27 + /* 28 + * Maximum time in milliseconds the cros_ec_ucsi driver 29 + * will wait for a response to a command or and ack. 30 + */ 31 + #define WRITE_TMO_MS 5000 32 + 33 + /* Number of times to attempt recovery from a write timeout before giving up. */ 34 + #define WRITE_TMO_CTR_MAX 5 35 + 36 + struct cros_ucsi_data { 37 + struct device *dev; 38 + struct ucsi *ucsi; 39 + 40 + struct cros_ec_device *ec; 41 + struct notifier_block nb; 42 + struct work_struct work; 43 + struct delayed_work write_tmo; 44 + int tmo_counter; 45 + 46 + struct completion complete; 47 + unsigned long flags; 48 + }; 49 + 50 + static int cros_ucsi_read(struct ucsi *ucsi, unsigned int offset, void *val, 51 + size_t val_len) 52 + { 53 + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); 54 + struct ec_params_ucsi_ppm_get req = { 55 + .offset = offset, 56 + .size = val_len, 57 + }; 58 + int ret; 59 + 60 + if (val_len > MAX_EC_DATA_SIZE) { 61 + dev_err(udata->dev, "Can't read %zu bytes. Too big.", val_len); 62 + return -EINVAL; 63 + } 64 + 65 + ret = cros_ec_cmd(udata->ec, 0, EC_CMD_UCSI_PPM_GET, 66 + &req, sizeof(req), val, val_len); 67 + if (ret < 0) { 68 + dev_warn(udata->dev, "Failed to send EC message UCSI_PPM_GET: error=%d", ret); 69 + return ret; 70 + } 71 + return 0; 72 + } 73 + 74 + static int cros_ucsi_read_version(struct ucsi *ucsi, u16 *version) 75 + { 76 + return cros_ucsi_read(ucsi, UCSI_VERSION, version, sizeof(*version)); 77 + } 78 + 79 + static int cros_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) 80 + { 81 + return cros_ucsi_read(ucsi, UCSI_CCI, cci, sizeof(*cci)); 82 + } 83 + 84 + static int cros_ucsi_read_message_in(struct ucsi *ucsi, void *val, 85 + size_t val_len) 86 + { 87 + return cros_ucsi_read(ucsi, UCSI_MESSAGE_IN, val, val_len); 88 + } 89 + 90 + static int cros_ucsi_async_control(struct ucsi *ucsi, u64 cmd) 91 + { 92 + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); 93 + u8 ec_buf[sizeof(struct ec_params_ucsi_ppm_set) + sizeof(cmd)]; 94 + struct ec_params_ucsi_ppm_set *req = (struct ec_params_ucsi_ppm_set *) ec_buf; 95 + int ret; 96 + 97 + req->offset = UCSI_CONTROL; 98 + memcpy(req->data, &cmd, sizeof(cmd)); 99 + ret = cros_ec_cmd(udata->ec, 0, EC_CMD_UCSI_PPM_SET, 100 + req, sizeof(ec_buf), NULL, 0); 101 + if (ret < 0) { 102 + dev_warn(udata->dev, "Failed to send EC message UCSI_PPM_SET: error=%d", ret); 103 + return ret; 104 + } 105 + return 0; 106 + } 107 + 108 + static int cros_ucsi_sync_control(struct ucsi *ucsi, u64 cmd) 109 + { 110 + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); 111 + int ret; 112 + 113 + ret = ucsi_sync_control_common(ucsi, cmd); 114 + switch (ret) { 115 + case -EBUSY: 116 + /* EC may return -EBUSY if CCI.busy is set. 117 + * Convert this to a timeout. 118 + */ 119 + case -ETIMEDOUT: 120 + /* Schedule recovery attempt when we timeout 121 + * or tried to send a command while still busy. 122 + */ 123 + cancel_delayed_work_sync(&udata->write_tmo); 124 + schedule_delayed_work(&udata->write_tmo, 125 + msecs_to_jiffies(WRITE_TMO_MS)); 126 + break; 127 + case 0: 128 + /* Successful write. Cancel any pending recovery work. */ 129 + cancel_delayed_work_sync(&udata->write_tmo); 130 + break; 131 + } 132 + 133 + return ret; 134 + } 135 + 136 + struct ucsi_operations cros_ucsi_ops = { 137 + .read_version = cros_ucsi_read_version, 138 + .read_cci = cros_ucsi_read_cci, 139 + .read_message_in = cros_ucsi_read_message_in, 140 + .async_control = cros_ucsi_async_control, 141 + .sync_control = cros_ucsi_sync_control, 142 + }; 143 + 144 + static void cros_ucsi_work(struct work_struct *work) 145 + { 146 + struct cros_ucsi_data *udata = container_of(work, struct cros_ucsi_data, work); 147 + u32 cci; 148 + 149 + if (cros_ucsi_read_cci(udata->ucsi, &cci)) 150 + return; 151 + 152 + ucsi_notify_common(udata->ucsi, cci); 153 + } 154 + 155 + static void cros_ucsi_write_timeout(struct work_struct *work) 156 + { 157 + struct cros_ucsi_data *udata = 158 + container_of(work, struct cros_ucsi_data, write_tmo.work); 159 + u32 cci; 160 + u64 cmd; 161 + 162 + if (cros_ucsi_read(udata->ucsi, UCSI_CCI, &cci, sizeof(cci))) { 163 + dev_err(udata->dev, 164 + "Reading CCI failed; no write timeout recovery possible."); 165 + return; 166 + } 167 + 168 + if (cci & UCSI_CCI_BUSY) { 169 + udata->tmo_counter++; 170 + 171 + if (udata->tmo_counter <= WRITE_TMO_CTR_MAX) 172 + schedule_delayed_work(&udata->write_tmo, 173 + msecs_to_jiffies(WRITE_TMO_MS)); 174 + else 175 + dev_err(udata->dev, 176 + "PPM unresponsive - too many write timeouts."); 177 + 178 + return; 179 + } 180 + 181 + /* No longer busy means we can reset our timeout counter. */ 182 + udata->tmo_counter = 0; 183 + 184 + /* Need to ack previous command which may have timed out. */ 185 + if (cci & UCSI_CCI_COMMAND_COMPLETE) { 186 + cmd = UCSI_ACK_CC_CI | UCSI_ACK_COMMAND_COMPLETE; 187 + cros_ucsi_async_control(udata->ucsi, cmd); 188 + 189 + /* Check again after a few seconds that the system has 190 + * recovered to make sure our async write above was successful. 191 + */ 192 + schedule_delayed_work(&udata->write_tmo, 193 + msecs_to_jiffies(WRITE_TMO_MS)); 194 + return; 195 + } 196 + 197 + /* We recovered from a previous timeout. Treat this as a recovery from 198 + * suspend and call resume. 199 + */ 200 + ucsi_resume(udata->ucsi); 201 + } 202 + 203 + static int cros_ucsi_event(struct notifier_block *nb, 204 + unsigned long host_event, void *_notify) 205 + { 206 + struct cros_ucsi_data *udata = container_of(nb, struct cros_ucsi_data, nb); 207 + 208 + if (!(host_event & PD_EVENT_PPM)) 209 + return NOTIFY_OK; 210 + 211 + dev_dbg(udata->dev, "UCSI notification received"); 212 + flush_work(&udata->work); 213 + schedule_work(&udata->work); 214 + 215 + return NOTIFY_OK; 216 + } 217 + 218 + static void cros_ucsi_destroy(struct cros_ucsi_data *udata) 219 + { 220 + cros_usbpd_unregister_notify(&udata->nb); 221 + cancel_delayed_work_sync(&udata->write_tmo); 222 + cancel_work_sync(&udata->work); 223 + ucsi_destroy(udata->ucsi); 224 + } 225 + 226 + static int cros_ucsi_probe(struct platform_device *pdev) 227 + { 228 + struct device *dev = &pdev->dev; 229 + struct cros_ec_dev *ec_data = dev_get_drvdata(dev->parent); 230 + struct cros_ucsi_data *udata; 231 + int ret; 232 + 233 + udata = devm_kzalloc(dev, sizeof(*udata), GFP_KERNEL); 234 + if (!udata) 235 + return -ENOMEM; 236 + 237 + udata->dev = dev; 238 + 239 + udata->ec = ec_data->ec_dev; 240 + if (!udata->ec) { 241 + dev_err(dev, "couldn't find parent EC device"); 242 + return -ENODEV; 243 + } 244 + 245 + platform_set_drvdata(pdev, udata); 246 + 247 + INIT_WORK(&udata->work, cros_ucsi_work); 248 + INIT_DELAYED_WORK(&udata->write_tmo, cros_ucsi_write_timeout); 249 + init_completion(&udata->complete); 250 + 251 + udata->ucsi = ucsi_create(dev, &cros_ucsi_ops); 252 + if (IS_ERR(udata->ucsi)) { 253 + dev_err(dev, "failed to allocate UCSI instance"); 254 + return PTR_ERR(udata->ucsi); 255 + } 256 + 257 + ucsi_set_drvdata(udata->ucsi, udata); 258 + 259 + udata->nb.notifier_call = cros_ucsi_event; 260 + ret = cros_usbpd_register_notify(&udata->nb); 261 + if (ret) { 262 + dev_err(dev, "failed to register notifier: error=%d", ret); 263 + ucsi_destroy(udata->ucsi); 264 + return ret; 265 + } 266 + 267 + ret = ucsi_register(udata->ucsi); 268 + if (ret) { 269 + dev_err(dev, "failed to register UCSI: error=%d", ret); 270 + cros_ucsi_destroy(udata); 271 + return ret; 272 + } 273 + 274 + return 0; 275 + } 276 + 277 + static void cros_ucsi_remove(struct platform_device *dev) 278 + { 279 + struct cros_ucsi_data *udata = platform_get_drvdata(dev); 280 + 281 + ucsi_unregister(udata->ucsi); 282 + cros_ucsi_destroy(udata); 283 + } 284 + 285 + static int __maybe_unused cros_ucsi_suspend(struct device *dev) 286 + { 287 + struct cros_ucsi_data *udata = dev_get_drvdata(dev); 288 + 289 + cancel_delayed_work_sync(&udata->write_tmo); 290 + cancel_work_sync(&udata->work); 291 + 292 + return 0; 293 + } 294 + 295 + static void __maybe_unused cros_ucsi_complete(struct device *dev) 296 + { 297 + struct cros_ucsi_data *udata = dev_get_drvdata(dev); 298 + 299 + ucsi_resume(udata->ucsi); 300 + } 301 + 302 + /* 303 + * UCSI protocol is also used on ChromeOS platforms which reply on 304 + * cros_ec_lpc.c driver for communication with embedded controller (EC). 305 + * On such platforms communication with the EC is not available until 306 + * the .complete() callback of the cros_ec_lpc driver is executed. 307 + * For this reason we delay ucsi_resume() until the .complete() stage 308 + * otherwise UCSI SET_NOTIFICATION_ENABLE command will fail and we won't 309 + * receive any UCSI notifications from the EC where PPM is implemented. 310 + */ 311 + static const struct dev_pm_ops cros_ucsi_pm_ops = { 312 + #ifdef CONFIG_PM_SLEEP 313 + .suspend = cros_ucsi_suspend, 314 + .complete = cros_ucsi_complete, 315 + #endif 316 + }; 317 + 318 + static const struct platform_device_id cros_ucsi_id[] = { 319 + { KBUILD_MODNAME, 0 }, 320 + {} 321 + }; 322 + MODULE_DEVICE_TABLE(platform, cros_ucsi_id); 323 + 324 + static struct platform_driver cros_ucsi_driver = { 325 + .driver = { 326 + .name = KBUILD_MODNAME, 327 + .pm = &cros_ucsi_pm_ops, 328 + }, 329 + .id_table = cros_ucsi_id, 330 + .probe = cros_ucsi_probe, 331 + .remove = cros_ucsi_remove, 332 + }; 333 + 334 + module_platform_driver(cros_ucsi_driver); 335 + 336 + MODULE_LICENSE("GPL"); 337 + MODULE_DESCRIPTION("UCSI driver for ChromeOS EC");