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