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