1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Bus for USB Type-C Alternate Modes 4 * 5 * Copyright (C) 2018 Intel Corporation 6 * Author: Heikki Krogerus <[email protected]> 7 */ 8 9 #include <linux/usb/pd_vdo.h> 10 11 #include "bus.h" 12 13 static inline int 14 typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data) 15 { 16 struct typec_mux_state state; 17 18 if (!alt->mux) 19 return 0; 20 21 state.alt = &alt->adev; 22 state.mode = conf; 23 state.data = data; 24 25 return alt->mux->set(alt->mux, &state); 26 } 27 28 static int typec_altmode_set_state(struct typec_altmode *adev, 29 unsigned long conf, void *data) 30 { 31 bool is_port = is_typec_port(adev->dev.parent); 32 struct altmode *port_altmode; 33 int ret; 34 35 port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner; 36 37 ret = typec_altmode_set_mux(port_altmode, conf, data); 38 if (ret) 39 return ret; 40 41 blocking_notifier_call_chain(&port_altmode->nh, conf, NULL); 42 43 return 0; 44 } 45 46 /* -------------------------------------------------------------------------- */ 47 /* Common API */ 48 49 /** 50 * typec_altmode_notify - Communication between the OS and alternate mode driver 51 * @adev: Handle to the alternate mode 52 * @conf: Alternate mode specific configuration value 53 * @data: Alternate mode specific data 54 * 55 * The primary purpose for this function is to allow the alternate mode drivers 56 * to tell which pin configuration has been negotiated with the partner. That 57 * information will then be used for example to configure the muxes. 58 * Communication to the other direction is also possible, and low level device 59 * drivers can also send notifications to the alternate mode drivers. The actual 60 * communication will be specific for every SVID. 61 */ 62 int typec_altmode_notify(struct typec_altmode *adev, 63 unsigned long conf, void *data) 64 { 65 bool is_port; 66 struct altmode *altmode; 67 struct altmode *partner; 68 int ret; 69 70 if (!adev) 71 return 0; 72 73 altmode = to_altmode(adev); 74 75 if (!altmode->partner) 76 return -ENODEV; 77 78 is_port = is_typec_port(adev->dev.parent); 79 partner = altmode->partner; 80 81 ret = typec_altmode_set_mux(is_port ? altmode : partner, conf, data); 82 if (ret) 83 return ret; 84 85 blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh, 86 conf, data); 87 88 if (partner->adev.ops && partner->adev.ops->notify) 89 return partner->adev.ops->notify(&partner->adev, conf, data); 90 91 return 0; 92 } 93 EXPORT_SYMBOL_GPL(typec_altmode_notify); 94 95 /** 96 * typec_altmode_enter - Enter Mode 97 * @adev: The alternate mode 98 * @vdo: VDO for the Enter Mode command 99 * 100 * The alternate mode drivers use this function to enter mode. The port drivers 101 * use this to inform the alternate mode drivers that the partner has initiated 102 * Enter Mode command. If the alternate mode does not require VDO, @vdo must be 103 * NULL. 104 */ 105 int typec_altmode_enter(struct typec_altmode *adev, u32 *vdo) 106 { 107 struct altmode *partner = to_altmode(adev)->partner; 108 struct typec_altmode *pdev = &partner->adev; 109 int ret; 110 111 if (!adev || adev->active) 112 return 0; 113 114 if (!pdev->ops || !pdev->ops->enter) 115 return -EOPNOTSUPP; 116 117 if (is_typec_port(pdev->dev.parent) && !pdev->active) 118 return -EPERM; 119 120 /* Moving to USB Safe State */ 121 ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL); 122 if (ret) 123 return ret; 124 125 /* Enter Mode */ 126 return pdev->ops->enter(pdev, vdo); 127 } 128 EXPORT_SYMBOL_GPL(typec_altmode_enter); 129 130 /** 131 * typec_altmode_exit - Exit Mode 132 * @adev: The alternate mode 133 * 134 * The partner of @adev has initiated Exit Mode command. 135 */ 136 int typec_altmode_exit(struct typec_altmode *adev) 137 { 138 struct altmode *partner = to_altmode(adev)->partner; 139 struct typec_altmode *pdev = &partner->adev; 140 int ret; 141 142 if (!adev || !adev->active) 143 return 0; 144 145 if (!pdev->ops || !pdev->ops->enter) 146 return -EOPNOTSUPP; 147 148 /* Moving to USB Safe State */ 149 ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL); 150 if (ret) 151 return ret; 152 153 /* Exit Mode command */ 154 return pdev->ops->exit(pdev); 155 } 156 EXPORT_SYMBOL_GPL(typec_altmode_exit); 157 158 /** 159 * typec_altmode_attention - Attention command 160 * @adev: The alternate mode 161 * @vdo: VDO for the Attention command 162 * 163 * Notifies the partner of @adev about Attention command. 164 */ 165 void typec_altmode_attention(struct typec_altmode *adev, u32 vdo) 166 { 167 struct typec_altmode *pdev = &to_altmode(adev)->partner->adev; 168 169 if (pdev->ops && pdev->ops->attention) 170 pdev->ops->attention(pdev, vdo); 171 } 172 EXPORT_SYMBOL_GPL(typec_altmode_attention); 173 174 /** 175 * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner 176 * @adev: Alternate mode handle 177 * @header: VDM Header 178 * @vdo: Array of Vendor Defined Data Objects 179 * @count: Number of Data Objects 180 * 181 * The alternate mode drivers use this function for SVID specific communication 182 * with the partner. The port drivers use it to deliver the Structured VDMs 183 * received from the partners to the alternate mode drivers. 184 */ 185 int typec_altmode_vdm(struct typec_altmode *adev, 186 const u32 header, const u32 *vdo, int count) 187 { 188 struct typec_altmode *pdev; 189 struct altmode *altmode; 190 191 if (!adev) 192 return 0; 193 194 altmode = to_altmode(adev); 195 196 if (!altmode->partner) 197 return -ENODEV; 198 199 pdev = &altmode->partner->adev; 200 201 if (!pdev->ops || !pdev->ops->vdm) 202 return -EOPNOTSUPP; 203 204 return pdev->ops->vdm(pdev, header, vdo, count); 205 } 206 EXPORT_SYMBOL_GPL(typec_altmode_vdm); 207 208 const struct typec_altmode * 209 typec_altmode_get_partner(struct typec_altmode *adev) 210 { 211 return adev ? &to_altmode(adev)->partner->adev : NULL; 212 } 213 EXPORT_SYMBOL_GPL(typec_altmode_get_partner); 214 215 /* -------------------------------------------------------------------------- */ 216 /* API for the alternate mode drivers */ 217 218 /** 219 * typec_altmode_get_plug - Find cable plug alternate mode 220 * @adev: Handle to partner alternate mode 221 * @index: Cable plug index 222 * 223 * Increment reference count for cable plug alternate mode device. Returns 224 * handle to the cable plug alternate mode, or NULL if none is found. 225 */ 226 struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev, 227 enum typec_plug_index index) 228 { 229 struct altmode *port = to_altmode(adev)->partner; 230 231 if (port->plug[index]) { 232 get_device(&port->plug[index]->adev.dev); 233 return &port->plug[index]->adev; 234 } 235 236 return NULL; 237 } 238 EXPORT_SYMBOL_GPL(typec_altmode_get_plug); 239 240 /** 241 * typec_altmode_put_plug - Decrement cable plug alternate mode reference count 242 * @plug: Handle to the cable plug alternate mode 243 */ 244 void typec_altmode_put_plug(struct typec_altmode *plug) 245 { 246 if (plug) 247 put_device(&plug->dev); 248 } 249 EXPORT_SYMBOL_GPL(typec_altmode_put_plug); 250 251 int __typec_altmode_register_driver(struct typec_altmode_driver *drv, 252 struct module *module) 253 { 254 if (!drv->probe) 255 return -EINVAL; 256 257 drv->driver.owner = module; 258 drv->driver.bus = &typec_bus; 259 260 return driver_register(&drv->driver); 261 } 262 EXPORT_SYMBOL_GPL(__typec_altmode_register_driver); 263 264 void typec_altmode_unregister_driver(struct typec_altmode_driver *drv) 265 { 266 driver_unregister(&drv->driver); 267 } 268 EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver); 269 270 /* -------------------------------------------------------------------------- */ 271 /* API for the port drivers */ 272 273 /** 274 * typec_match_altmode - Match SVID and mode to an array of alternate modes 275 * @altmodes: Array of alternate modes 276 * @n: Number of elements in the array, or -1 for NULL terminated arrays 277 * @svid: Standard or Vendor ID to match with 278 * @mode: Mode to match with 279 * 280 * Return pointer to an alternate mode with SVID matching @svid, or NULL when no 281 * match is found. 282 */ 283 struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes, 284 size_t n, u16 svid, u8 mode) 285 { 286 int i; 287 288 for (i = 0; i < n; i++) { 289 if (!altmodes[i]) 290 break; 291 if (altmodes[i]->svid == svid && altmodes[i]->mode == mode) 292 return altmodes[i]; 293 } 294 295 return NULL; 296 } 297 EXPORT_SYMBOL_GPL(typec_match_altmode); 298 299 /* -------------------------------------------------------------------------- */ 300 301 static ssize_t 302 description_show(struct device *dev, struct device_attribute *attr, char *buf) 303 { 304 struct typec_altmode *alt = to_typec_altmode(dev); 305 306 return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); 307 } 308 static DEVICE_ATTR_RO(description); 309 310 static struct attribute *typec_attrs[] = { 311 &dev_attr_description.attr, 312 NULL 313 }; 314 ATTRIBUTE_GROUPS(typec); 315 316 static int typec_match(struct device *dev, struct device_driver *driver) 317 { 318 struct typec_altmode_driver *drv = to_altmode_driver(driver); 319 struct typec_altmode *altmode = to_typec_altmode(dev); 320 const struct typec_device_id *id; 321 322 for (id = drv->id_table; id->svid; id++) 323 if (id->svid == altmode->svid && 324 (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode)) 325 return 1; 326 return 0; 327 } 328 329 static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) 330 { 331 struct typec_altmode *altmode = to_typec_altmode(dev); 332 333 if (add_uevent_var(env, "SVID=%04X", altmode->svid)) 334 return -ENOMEM; 335 336 if (add_uevent_var(env, "MODE=%u", altmode->mode)) 337 return -ENOMEM; 338 339 return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X", 340 altmode->svid, altmode->mode); 341 } 342 343 static int typec_altmode_create_links(struct altmode *alt) 344 { 345 struct device *port_dev = &alt->partner->adev.dev; 346 struct device *dev = &alt->adev.dev; 347 int err; 348 349 err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port"); 350 if (err) 351 return err; 352 353 err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner"); 354 if (err) 355 sysfs_remove_link(&dev->kobj, "port"); 356 357 return err; 358 } 359 360 static void typec_altmode_remove_links(struct altmode *alt) 361 { 362 sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner"); 363 sysfs_remove_link(&alt->adev.dev.kobj, "port"); 364 } 365 366 static int typec_probe(struct device *dev) 367 { 368 struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); 369 struct typec_altmode *adev = to_typec_altmode(dev); 370 struct altmode *altmode = to_altmode(adev); 371 int ret; 372 373 /* Fail if the port does not support the alternate mode */ 374 if (!altmode->partner) 375 return -ENODEV; 376 377 ret = typec_altmode_create_links(altmode); 378 if (ret) { 379 dev_warn(dev, "failed to create symlinks\n"); 380 return ret; 381 } 382 383 ret = drv->probe(adev); 384 if (ret) 385 typec_altmode_remove_links(altmode); 386 387 return ret; 388 } 389 390 static int typec_remove(struct device *dev) 391 { 392 struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); 393 struct typec_altmode *adev = to_typec_altmode(dev); 394 struct altmode *altmode = to_altmode(adev); 395 396 typec_altmode_remove_links(altmode); 397 398 if (drv->remove) 399 drv->remove(to_typec_altmode(dev)); 400 401 if (adev->active) { 402 WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL)); 403 typec_altmode_update_active(adev, false); 404 } 405 406 adev->desc = NULL; 407 adev->ops = NULL; 408 409 return 0; 410 } 411 412 struct bus_type typec_bus = { 413 .name = "typec", 414 .dev_groups = typec_groups, 415 .match = typec_match, 416 .uevent = typec_uevent, 417 .probe = typec_probe, 418 .remove = typec_remove, 419 }; 420