Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Bus for USB Type-C Alternate Modes
0004  *
0005  * Copyright (C) 2018 Intel Corporation
0006  * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
0007  */
0008 
0009 #include <linux/usb/pd_vdo.h>
0010 
0011 #include "bus.h"
0012 #include "class.h"
0013 #include "mux.h"
0014 
0015 static inline int
0016 typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data)
0017 {
0018     struct typec_mux_state state;
0019 
0020     if (!alt->mux)
0021         return 0;
0022 
0023     state.alt = &alt->adev;
0024     state.mode = conf;
0025     state.data = data;
0026 
0027     return typec_mux_set(alt->mux, &state);
0028 }
0029 
0030 static int typec_altmode_set_state(struct typec_altmode *adev,
0031                    unsigned long conf, void *data)
0032 {
0033     bool is_port = is_typec_port(adev->dev.parent);
0034     struct altmode *port_altmode;
0035 
0036     port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner;
0037 
0038     return typec_altmode_set_mux(port_altmode, conf, data);
0039 }
0040 
0041 /* -------------------------------------------------------------------------- */
0042 /* Common API */
0043 
0044 /**
0045  * typec_altmode_notify - Communication between the OS and alternate mode driver
0046  * @adev: Handle to the alternate mode
0047  * @conf: Alternate mode specific configuration value
0048  * @data: Alternate mode specific data
0049  *
0050  * The primary purpose for this function is to allow the alternate mode drivers
0051  * to tell which pin configuration has been negotiated with the partner. That
0052  * information will then be used for example to configure the muxes.
0053  * Communication to the other direction is also possible, and low level device
0054  * drivers can also send notifications to the alternate mode drivers. The actual
0055  * communication will be specific for every SVID.
0056  */
0057 int typec_altmode_notify(struct typec_altmode *adev,
0058              unsigned long conf, void *data)
0059 {
0060     bool is_port;
0061     struct altmode *altmode;
0062     struct altmode *partner;
0063     int ret;
0064 
0065     if (!adev)
0066         return 0;
0067 
0068     altmode = to_altmode(adev);
0069 
0070     if (!altmode->partner)
0071         return -ENODEV;
0072 
0073     is_port = is_typec_port(adev->dev.parent);
0074     partner = altmode->partner;
0075 
0076     ret = typec_altmode_set_mux(is_port ? altmode : partner, conf, data);
0077     if (ret)
0078         return ret;
0079 
0080     if (partner->adev.ops && partner->adev.ops->notify)
0081         return partner->adev.ops->notify(&partner->adev, conf, data);
0082 
0083     return 0;
0084 }
0085 EXPORT_SYMBOL_GPL(typec_altmode_notify);
0086 
0087 /**
0088  * typec_altmode_enter - Enter Mode
0089  * @adev: The alternate mode
0090  * @vdo: VDO for the Enter Mode command
0091  *
0092  * The alternate mode drivers use this function to enter mode. The port drivers
0093  * use this to inform the alternate mode drivers that the partner has initiated
0094  * Enter Mode command. If the alternate mode does not require VDO, @vdo must be
0095  * NULL.
0096  */
0097 int typec_altmode_enter(struct typec_altmode *adev, u32 *vdo)
0098 {
0099     struct altmode *partner = to_altmode(adev)->partner;
0100     struct typec_altmode *pdev = &partner->adev;
0101     int ret;
0102 
0103     if (!adev || adev->active)
0104         return 0;
0105 
0106     if (!pdev->ops || !pdev->ops->enter)
0107         return -EOPNOTSUPP;
0108 
0109     if (is_typec_port(pdev->dev.parent) && !pdev->active)
0110         return -EPERM;
0111 
0112     /* Moving to USB Safe State */
0113     ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL);
0114     if (ret)
0115         return ret;
0116 
0117     /* Enter Mode */
0118     return pdev->ops->enter(pdev, vdo);
0119 }
0120 EXPORT_SYMBOL_GPL(typec_altmode_enter);
0121 
0122 /**
0123  * typec_altmode_exit - Exit Mode
0124  * @adev: The alternate mode
0125  *
0126  * The partner of @adev has initiated Exit Mode command.
0127  */
0128 int typec_altmode_exit(struct typec_altmode *adev)
0129 {
0130     struct altmode *partner = to_altmode(adev)->partner;
0131     struct typec_altmode *pdev = &partner->adev;
0132     int ret;
0133 
0134     if (!adev || !adev->active)
0135         return 0;
0136 
0137     if (!pdev->ops || !pdev->ops->enter)
0138         return -EOPNOTSUPP;
0139 
0140     /* Moving to USB Safe State */
0141     ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL);
0142     if (ret)
0143         return ret;
0144 
0145     /* Exit Mode command */
0146     return pdev->ops->exit(pdev);
0147 }
0148 EXPORT_SYMBOL_GPL(typec_altmode_exit);
0149 
0150 /**
0151  * typec_altmode_attention - Attention command
0152  * @adev: The alternate mode
0153  * @vdo: VDO for the Attention command
0154  *
0155  * Notifies the partner of @adev about Attention command.
0156  */
0157 void typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
0158 {
0159     struct typec_altmode *pdev = &to_altmode(adev)->partner->adev;
0160 
0161     if (pdev->ops && pdev->ops->attention)
0162         pdev->ops->attention(pdev, vdo);
0163 }
0164 EXPORT_SYMBOL_GPL(typec_altmode_attention);
0165 
0166 /**
0167  * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
0168  * @adev: Alternate mode handle
0169  * @header: VDM Header
0170  * @vdo: Array of Vendor Defined Data Objects
0171  * @count: Number of Data Objects
0172  *
0173  * The alternate mode drivers use this function for SVID specific communication
0174  * with the partner. The port drivers use it to deliver the Structured VDMs
0175  * received from the partners to the alternate mode drivers.
0176  */
0177 int typec_altmode_vdm(struct typec_altmode *adev,
0178               const u32 header, const u32 *vdo, int count)
0179 {
0180     struct typec_altmode *pdev;
0181     struct altmode *altmode;
0182 
0183     if (!adev)
0184         return 0;
0185 
0186     altmode = to_altmode(adev);
0187 
0188     if (!altmode->partner)
0189         return -ENODEV;
0190 
0191     pdev = &altmode->partner->adev;
0192 
0193     if (!pdev->ops || !pdev->ops->vdm)
0194         return -EOPNOTSUPP;
0195 
0196     return pdev->ops->vdm(pdev, header, vdo, count);
0197 }
0198 EXPORT_SYMBOL_GPL(typec_altmode_vdm);
0199 
0200 const struct typec_altmode *
0201 typec_altmode_get_partner(struct typec_altmode *adev)
0202 {
0203     if (!adev || !to_altmode(adev)->partner)
0204         return NULL;
0205 
0206     return &to_altmode(adev)->partner->adev;
0207 }
0208 EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
0209 
0210 /* -------------------------------------------------------------------------- */
0211 /* API for the alternate mode drivers */
0212 
0213 /**
0214  * typec_altmode_get_plug - Find cable plug alternate mode
0215  * @adev: Handle to partner alternate mode
0216  * @index: Cable plug index
0217  *
0218  * Increment reference count for cable plug alternate mode device. Returns
0219  * handle to the cable plug alternate mode, or NULL if none is found.
0220  */
0221 struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev,
0222                          enum typec_plug_index index)
0223 {
0224     struct altmode *port = to_altmode(adev)->partner;
0225 
0226     if (port->plug[index]) {
0227         get_device(&port->plug[index]->adev.dev);
0228         return &port->plug[index]->adev;
0229     }
0230 
0231     return NULL;
0232 }
0233 EXPORT_SYMBOL_GPL(typec_altmode_get_plug);
0234 
0235 /**
0236  * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
0237  * @plug: Handle to the cable plug alternate mode
0238  */
0239 void typec_altmode_put_plug(struct typec_altmode *plug)
0240 {
0241     if (plug)
0242         put_device(&plug->dev);
0243 }
0244 EXPORT_SYMBOL_GPL(typec_altmode_put_plug);
0245 
0246 int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
0247                     struct module *module)
0248 {
0249     if (!drv->probe)
0250         return -EINVAL;
0251 
0252     drv->driver.owner = module;
0253     drv->driver.bus = &typec_bus;
0254 
0255     return driver_register(&drv->driver);
0256 }
0257 EXPORT_SYMBOL_GPL(__typec_altmode_register_driver);
0258 
0259 void typec_altmode_unregister_driver(struct typec_altmode_driver *drv)
0260 {
0261     driver_unregister(&drv->driver);
0262 }
0263 EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver);
0264 
0265 /* -------------------------------------------------------------------------- */
0266 /* API for the port drivers */
0267 
0268 /**
0269  * typec_match_altmode - Match SVID and mode to an array of alternate modes
0270  * @altmodes: Array of alternate modes
0271  * @n: Number of elements in the array, or -1 for NULL terminated arrays
0272  * @svid: Standard or Vendor ID to match with
0273  * @mode: Mode to match with
0274  *
0275  * Return pointer to an alternate mode with SVID matching @svid, or NULL when no
0276  * match is found.
0277  */
0278 struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
0279                       size_t n, u16 svid, u8 mode)
0280 {
0281     int i;
0282 
0283     for (i = 0; i < n; i++) {
0284         if (!altmodes[i])
0285             break;
0286         if (altmodes[i]->svid == svid && altmodes[i]->mode == mode)
0287             return altmodes[i];
0288     }
0289 
0290     return NULL;
0291 }
0292 EXPORT_SYMBOL_GPL(typec_match_altmode);
0293 
0294 /* -------------------------------------------------------------------------- */
0295 
0296 static ssize_t
0297 description_show(struct device *dev, struct device_attribute *attr, char *buf)
0298 {
0299     struct typec_altmode *alt = to_typec_altmode(dev);
0300 
0301     return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
0302 }
0303 static DEVICE_ATTR_RO(description);
0304 
0305 static struct attribute *typec_attrs[] = {
0306     &dev_attr_description.attr,
0307     NULL
0308 };
0309 ATTRIBUTE_GROUPS(typec);
0310 
0311 static int typec_match(struct device *dev, struct device_driver *driver)
0312 {
0313     struct typec_altmode_driver *drv = to_altmode_driver(driver);
0314     struct typec_altmode *altmode = to_typec_altmode(dev);
0315     const struct typec_device_id *id;
0316 
0317     for (id = drv->id_table; id->svid; id++)
0318         if (id->svid == altmode->svid &&
0319             (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode))
0320             return 1;
0321     return 0;
0322 }
0323 
0324 static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
0325 {
0326     struct typec_altmode *altmode = to_typec_altmode(dev);
0327 
0328     if (add_uevent_var(env, "SVID=%04X", altmode->svid))
0329         return -ENOMEM;
0330 
0331     if (add_uevent_var(env, "MODE=%u", altmode->mode))
0332         return -ENOMEM;
0333 
0334     return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X",
0335                   altmode->svid, altmode->mode);
0336 }
0337 
0338 static int typec_altmode_create_links(struct altmode *alt)
0339 {
0340     struct device *port_dev = &alt->partner->adev.dev;
0341     struct device *dev = &alt->adev.dev;
0342     int err;
0343 
0344     err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port");
0345     if (err)
0346         return err;
0347 
0348     err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner");
0349     if (err)
0350         sysfs_remove_link(&dev->kobj, "port");
0351 
0352     return err;
0353 }
0354 
0355 static void typec_altmode_remove_links(struct altmode *alt)
0356 {
0357     sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner");
0358     sysfs_remove_link(&alt->adev.dev.kobj, "port");
0359 }
0360 
0361 static int typec_probe(struct device *dev)
0362 {
0363     struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
0364     struct typec_altmode *adev = to_typec_altmode(dev);
0365     struct altmode *altmode = to_altmode(adev);
0366     int ret;
0367 
0368     /* Fail if the port does not support the alternate mode */
0369     if (!altmode->partner)
0370         return -ENODEV;
0371 
0372     ret = typec_altmode_create_links(altmode);
0373     if (ret) {
0374         dev_warn(dev, "failed to create symlinks\n");
0375         return ret;
0376     }
0377 
0378     ret = drv->probe(adev);
0379     if (ret)
0380         typec_altmode_remove_links(altmode);
0381 
0382     return ret;
0383 }
0384 
0385 static void typec_remove(struct device *dev)
0386 {
0387     struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
0388     struct typec_altmode *adev = to_typec_altmode(dev);
0389     struct altmode *altmode = to_altmode(adev);
0390 
0391     typec_altmode_remove_links(altmode);
0392 
0393     if (drv->remove)
0394         drv->remove(to_typec_altmode(dev));
0395 
0396     if (adev->active) {
0397         WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL));
0398         typec_altmode_update_active(adev, false);
0399     }
0400 
0401     adev->desc = NULL;
0402     adev->ops = NULL;
0403 }
0404 
0405 struct bus_type typec_bus = {
0406     .name = "typec",
0407     .dev_groups = typec_groups,
0408     .match = typec_match,
0409     .uevent = typec_uevent,
0410     .probe = typec_probe,
0411     .remove = typec_remove,
0412 };