123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- // SPDX-License-Identifier: GPL-2.0
- /**
- * Bus for USB Type-C Alternate Modes
- *
- * Copyright (C) 2018 Intel Corporation
- * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
- */
- #include <linux/usb/pd_vdo.h>
- #include "bus.h"
- static inline int typec_altmode_set_mux(struct altmode *alt, u8 state)
- {
- return alt->mux ? alt->mux->set(alt->mux, state) : 0;
- }
- static int typec_altmode_set_state(struct typec_altmode *adev, int state)
- {
- bool is_port = is_typec_port(adev->dev.parent);
- struct altmode *port_altmode;
- int ret;
- port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner;
- ret = typec_altmode_set_mux(port_altmode, state);
- if (ret)
- return ret;
- blocking_notifier_call_chain(&port_altmode->nh, state, NULL);
- return 0;
- }
- /* -------------------------------------------------------------------------- */
- /* Common API */
- /**
- * typec_altmode_notify - Communication between the OS and alternate mode driver
- * @adev: Handle to the alternate mode
- * @conf: Alternate mode specific configuration value
- * @data: Alternate mode specific data
- *
- * The primary purpose for this function is to allow the alternate mode drivers
- * to tell which pin configuration has been negotiated with the partner. That
- * information will then be used for example to configure the muxes.
- * Communication to the other direction is also possible, and low level device
- * drivers can also send notifications to the alternate mode drivers. The actual
- * communication will be specific for every SVID.
- */
- int typec_altmode_notify(struct typec_altmode *adev,
- unsigned long conf, void *data)
- {
- bool is_port;
- struct altmode *altmode;
- struct altmode *partner;
- int ret;
- if (!adev)
- return 0;
- altmode = to_altmode(adev);
- if (!altmode->partner)
- return -ENODEV;
- is_port = is_typec_port(adev->dev.parent);
- partner = altmode->partner;
- ret = typec_altmode_set_mux(is_port ? altmode : partner, (u8)conf);
- if (ret)
- return ret;
- blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh,
- conf, data);
- if (partner->adev.ops && partner->adev.ops->notify)
- return partner->adev.ops->notify(&partner->adev, conf, data);
- return 0;
- }
- EXPORT_SYMBOL_GPL(typec_altmode_notify);
- /**
- * typec_altmode_enter - Enter Mode
- * @adev: The alternate mode
- *
- * The alternate mode drivers use this function to enter mode. The port drivers
- * use this to inform the alternate mode drivers that the partner has initiated
- * Enter Mode command.
- */
- int typec_altmode_enter(struct typec_altmode *adev)
- {
- struct altmode *partner = to_altmode(adev)->partner;
- struct typec_altmode *pdev = &partner->adev;
- int ret;
- if (!adev || adev->active)
- return 0;
- if (!pdev->ops || !pdev->ops->enter)
- return -EOPNOTSUPP;
- /* Moving to USB Safe State */
- ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
- if (ret)
- return ret;
- /* Enter Mode */
- return pdev->ops->enter(pdev);
- }
- EXPORT_SYMBOL_GPL(typec_altmode_enter);
- /**
- * typec_altmode_exit - Exit Mode
- * @adev: The alternate mode
- *
- * The partner of @adev has initiated Exit Mode command.
- */
- int typec_altmode_exit(struct typec_altmode *adev)
- {
- struct altmode *partner = to_altmode(adev)->partner;
- struct typec_altmode *pdev = &partner->adev;
- int ret;
- if (!adev || !adev->active)
- return 0;
- if (!pdev->ops || !pdev->ops->enter)
- return -EOPNOTSUPP;
- /* Moving to USB Safe State */
- ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
- if (ret)
- return ret;
- /* Exit Mode command */
- return pdev->ops->exit(pdev);
- }
- EXPORT_SYMBOL_GPL(typec_altmode_exit);
- /**
- * typec_altmode_attention - Attention command
- * @adev: The alternate mode
- * @vdo: VDO for the Attention command
- *
- * Notifies the partner of @adev about Attention command.
- */
- void typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
- {
- struct typec_altmode *pdev = &to_altmode(adev)->partner->adev;
- if (pdev->ops && pdev->ops->attention)
- pdev->ops->attention(pdev, vdo);
- }
- EXPORT_SYMBOL_GPL(typec_altmode_attention);
- /**
- * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
- * @adev: Alternate mode handle
- * @header: VDM Header
- * @vdo: Array of Vendor Defined Data Objects
- * @count: Number of Data Objects
- *
- * The alternate mode drivers use this function for SVID specific communication
- * with the partner. The port drivers use it to deliver the Structured VDMs
- * received from the partners to the alternate mode drivers.
- */
- int typec_altmode_vdm(struct typec_altmode *adev,
- const u32 header, const u32 *vdo, int count)
- {
- struct typec_altmode *pdev;
- struct altmode *altmode;
- if (!adev)
- return 0;
- altmode = to_altmode(adev);
- if (!altmode->partner)
- return -ENODEV;
- pdev = &altmode->partner->adev;
- if (!pdev->ops || !pdev->ops->vdm)
- return -EOPNOTSUPP;
- return pdev->ops->vdm(pdev, header, vdo, count);
- }
- EXPORT_SYMBOL_GPL(typec_altmode_vdm);
- const struct typec_altmode *
- typec_altmode_get_partner(struct typec_altmode *adev)
- {
- return &to_altmode(adev)->partner->adev;
- }
- EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
- /* -------------------------------------------------------------------------- */
- /* API for the alternate mode drivers */
- /**
- * typec_altmode_get_plug - Find cable plug alternate mode
- * @adev: Handle to partner alternate mode
- * @index: Cable plug index
- *
- * Increment reference count for cable plug alternate mode device. Returns
- * handle to the cable plug alternate mode, or NULL if none is found.
- */
- struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev,
- enum typec_plug_index index)
- {
- struct altmode *port = to_altmode(adev)->partner;
- if (port->plug[index]) {
- get_device(&port->plug[index]->adev.dev);
- return &port->plug[index]->adev;
- }
- return NULL;
- }
- EXPORT_SYMBOL_GPL(typec_altmode_get_plug);
- /**
- * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
- * @plug: Handle to the cable plug alternate mode
- */
- void typec_altmode_put_plug(struct typec_altmode *plug)
- {
- if (plug)
- put_device(&plug->dev);
- }
- EXPORT_SYMBOL_GPL(typec_altmode_put_plug);
- int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
- struct module *module)
- {
- if (!drv->probe)
- return -EINVAL;
- drv->driver.owner = module;
- drv->driver.bus = &typec_bus;
- return driver_register(&drv->driver);
- }
- EXPORT_SYMBOL_GPL(__typec_altmode_register_driver);
- void typec_altmode_unregister_driver(struct typec_altmode_driver *drv)
- {
- driver_unregister(&drv->driver);
- }
- EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver);
- /* -------------------------------------------------------------------------- */
- /* API for the port drivers */
- /**
- * typec_match_altmode - Match SVID and mode to an array of alternate modes
- * @altmodes: Array of alternate modes
- * @n: Number of elements in the array, or -1 for NULL terminated arrays
- * @svid: Standard or Vendor ID to match with
- * @mode: Mode to match with
- *
- * Return pointer to an alternate mode with SVID matching @svid, or NULL when no
- * match is found.
- */
- struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
- size_t n, u16 svid, u8 mode)
- {
- int i;
- for (i = 0; i < n; i++) {
- if (!altmodes[i])
- break;
- if (altmodes[i]->svid == svid && altmodes[i]->mode == mode)
- return altmodes[i];
- }
- return NULL;
- }
- EXPORT_SYMBOL_GPL(typec_match_altmode);
- /* -------------------------------------------------------------------------- */
- static ssize_t
- description_show(struct device *dev, struct device_attribute *attr, char *buf)
- {
- struct typec_altmode *alt = to_typec_altmode(dev);
- return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
- }
- static DEVICE_ATTR_RO(description);
- static struct attribute *typec_attrs[] = {
- &dev_attr_description.attr,
- NULL
- };
- ATTRIBUTE_GROUPS(typec);
- static int typec_match(struct device *dev, struct device_driver *driver)
- {
- struct typec_altmode_driver *drv = to_altmode_driver(driver);
- struct typec_altmode *altmode = to_typec_altmode(dev);
- const struct typec_device_id *id;
- for (id = drv->id_table; id->svid; id++)
- if (id->svid == altmode->svid &&
- (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode))
- return 1;
- return 0;
- }
- static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
- {
- struct typec_altmode *altmode = to_typec_altmode(dev);
- if (add_uevent_var(env, "SVID=%04X", altmode->svid))
- return -ENOMEM;
- if (add_uevent_var(env, "MODE=%u", altmode->mode))
- return -ENOMEM;
- return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X",
- altmode->svid, altmode->mode);
- }
- static int typec_altmode_create_links(struct altmode *alt)
- {
- struct device *port_dev = &alt->partner->adev.dev;
- struct device *dev = &alt->adev.dev;
- int err;
- err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port");
- if (err)
- return err;
- err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner");
- if (err)
- sysfs_remove_link(&dev->kobj, "port");
- return err;
- }
- static void typec_altmode_remove_links(struct altmode *alt)
- {
- sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner");
- sysfs_remove_link(&alt->adev.dev.kobj, "port");
- }
- static int typec_probe(struct device *dev)
- {
- struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
- struct typec_altmode *adev = to_typec_altmode(dev);
- struct altmode *altmode = to_altmode(adev);
- int ret;
- /* Fail if the port does not support the alternate mode */
- if (!altmode->partner)
- return -ENODEV;
- ret = typec_altmode_create_links(altmode);
- if (ret) {
- dev_warn(dev, "failed to create symlinks\n");
- return ret;
- }
- ret = drv->probe(adev);
- if (ret)
- typec_altmode_remove_links(altmode);
- return ret;
- }
- static int typec_remove(struct device *dev)
- {
- struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
- struct typec_altmode *adev = to_typec_altmode(dev);
- struct altmode *altmode = to_altmode(adev);
- typec_altmode_remove_links(altmode);
- if (drv->remove)
- drv->remove(to_typec_altmode(dev));
- if (adev->active) {
- WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE));
- typec_altmode_update_active(adev, false);
- }
- adev->desc = NULL;
- adev->ops = NULL;
- return 0;
- }
- struct bus_type typec_bus = {
- .name = "typec",
- .dev_groups = typec_groups,
- .match = typec_match,
- .uevent = typec_uevent,
- .probe = typec_probe,
- .remove = typec_remove,
- };
|