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