| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * USB Power Delivery sysfs entries | |
| * | |
| * Copyright (C) 2022, Intel Corporation | |
| * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> | |
| */ | |
| #include <linux/slab.h> | |
| #include <linux/usb/pd.h> | |
| #include "pd.h" | |
| static DEFINE_IDA(pd_ida); | |
| static struct class pd_class = { | |
| .name = "usb_power_delivery", | |
| }; | |
| #define to_pdo(o) container_of(o, struct pdo, dev) | |
| struct pdo { | |
| struct device dev; | |
| int object_position; | |
| u32 pdo; | |
| }; | |
| static void pdo_release(struct device *dev) | |
| { | |
| kfree(to_pdo(dev)); | |
| } | |
| /* -------------------------------------------------------------------------- */ | |
| /* Fixed Supply */ | |
| static ssize_t | |
| dual_role_power_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DUAL_ROLE)); | |
| } | |
| static DEVICE_ATTR_RO(dual_role_power); | |
| static ssize_t | |
| usb_suspend_supported_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_SUSPEND)); | |
| } | |
| static DEVICE_ATTR_RO(usb_suspend_supported); | |
| static ssize_t | |
| higher_capability_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_HIGHER_CAP)); | |
| } | |
| static DEVICE_ATTR_RO(higher_capability); | |
| static ssize_t | |
| unconstrained_power_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_EXTPOWER)); | |
| } | |
| static DEVICE_ATTR_RO(unconstrained_power); | |
| static ssize_t | |
| usb_communication_capable_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_USB_COMM)); | |
| } | |
| static DEVICE_ATTR_RO(usb_communication_capable); | |
| static ssize_t | |
| dual_role_data_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DATA_SWAP)); | |
| } | |
| static DEVICE_ATTR_RO(dual_role_data); | |
| static ssize_t | |
| unchunked_extended_messages_supported_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_UNCHUNK_EXT)); | |
| } | |
| static DEVICE_ATTR_RO(unchunked_extended_messages_supported); | |
| static ssize_t | |
| peak_current_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", (to_pdo(dev)->pdo >> PDO_FIXED_PEAK_CURR_SHIFT) & 3); | |
| } | |
| static DEVICE_ATTR_RO(peak_current); | |
| static ssize_t | |
| fast_role_swap_current_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", (to_pdo(dev)->pdo >> PDO_FIXED_FRS_CURR_SHIFT) & 3); | |
| } | |
| static DEVICE_ATTR_RO(fast_role_swap_current); | |
| static ssize_t voltage_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umV\n", pdo_fixed_voltage(to_pdo(dev)->pdo)); | |
| } | |
| static DEVICE_ATTR_RO(voltage); | |
| /* Shared with Variable supplies, both source and sink */ | |
| static ssize_t current_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umA\n", pdo_max_current(to_pdo(dev)->pdo)); | |
| } | |
| /* Shared with Variable type supplies */ | |
| static struct device_attribute maximum_current_attr = { | |
| .attr = { | |
| .name = "maximum_current", | |
| .mode = 0444, | |
| }, | |
| .show = current_show, | |
| }; | |
| static struct device_attribute operational_current_attr = { | |
| .attr = { | |
| .name = "operational_current", | |
| .mode = 0444, | |
| }, | |
| .show = current_show, | |
| }; | |
| static struct attribute *source_fixed_supply_attrs[] = { | |
| &dev_attr_dual_role_power.attr, | |
| &dev_attr_usb_suspend_supported.attr, | |
| &dev_attr_unconstrained_power.attr, | |
| &dev_attr_usb_communication_capable.attr, | |
| &dev_attr_dual_role_data.attr, | |
| &dev_attr_unchunked_extended_messages_supported.attr, | |
| &dev_attr_peak_current.attr, | |
| &dev_attr_voltage.attr, | |
| &maximum_current_attr.attr, | |
| NULL | |
| }; | |
| static umode_t fixed_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) | |
| { | |
| if (to_pdo(kobj_to_dev(kobj))->object_position && | |
| attr != &dev_attr_peak_current.attr && | |
| attr != &dev_attr_voltage.attr && | |
| attr != &maximum_current_attr.attr && | |
| attr != &operational_current_attr.attr) | |
| return 0; | |
| return attr->mode; | |
| } | |
| static const struct attribute_group source_fixed_supply_group = { | |
| .is_visible = fixed_attr_is_visible, | |
| .attrs = source_fixed_supply_attrs, | |
| }; | |
| __ATTRIBUTE_GROUPS(source_fixed_supply); | |
| static const struct device_type source_fixed_supply_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = source_fixed_supply_groups, | |
| }; | |
| static struct attribute *sink_fixed_supply_attrs[] = { | |
| &dev_attr_dual_role_power.attr, | |
| &dev_attr_higher_capability.attr, | |
| &dev_attr_unconstrained_power.attr, | |
| &dev_attr_usb_communication_capable.attr, | |
| &dev_attr_dual_role_data.attr, | |
| &dev_attr_unchunked_extended_messages_supported.attr, | |
| &dev_attr_fast_role_swap_current.attr, | |
| &dev_attr_voltage.attr, | |
| &operational_current_attr.attr, | |
| NULL | |
| }; | |
| static const struct attribute_group sink_fixed_supply_group = { | |
| .is_visible = fixed_attr_is_visible, | |
| .attrs = sink_fixed_supply_attrs, | |
| }; | |
| __ATTRIBUTE_GROUPS(sink_fixed_supply); | |
| static const struct device_type sink_fixed_supply_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = sink_fixed_supply_groups, | |
| }; | |
| /* -------------------------------------------------------------------------- */ | |
| /* Variable Supply */ | |
| static ssize_t | |
| maximum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umV\n", pdo_max_voltage(to_pdo(dev)->pdo)); | |
| } | |
| static DEVICE_ATTR_RO(maximum_voltage); | |
| static ssize_t | |
| minimum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umV\n", pdo_min_voltage(to_pdo(dev)->pdo)); | |
| } | |
| static DEVICE_ATTR_RO(minimum_voltage); | |
| static struct attribute *source_variable_supply_attrs[] = { | |
| &dev_attr_maximum_voltage.attr, | |
| &dev_attr_minimum_voltage.attr, | |
| &maximum_current_attr.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(source_variable_supply); | |
| static const struct device_type source_variable_supply_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = source_variable_supply_groups, | |
| }; | |
| static struct attribute *sink_variable_supply_attrs[] = { | |
| &dev_attr_maximum_voltage.attr, | |
| &dev_attr_minimum_voltage.attr, | |
| &operational_current_attr.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(sink_variable_supply); | |
| static const struct device_type sink_variable_supply_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = sink_variable_supply_groups, | |
| }; | |
| /* -------------------------------------------------------------------------- */ | |
| /* Battery */ | |
| static ssize_t | |
| maximum_power_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); | |
| } | |
| static DEVICE_ATTR_RO(maximum_power); | |
| static ssize_t | |
| operational_power_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); | |
| } | |
| static DEVICE_ATTR_RO(operational_power); | |
| static struct attribute *source_battery_attrs[] = { | |
| &dev_attr_maximum_voltage.attr, | |
| &dev_attr_minimum_voltage.attr, | |
| &dev_attr_maximum_power.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(source_battery); | |
| static const struct device_type source_battery_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = source_battery_groups, | |
| }; | |
| static struct attribute *sink_battery_attrs[] = { | |
| &dev_attr_maximum_voltage.attr, | |
| &dev_attr_minimum_voltage.attr, | |
| &dev_attr_operational_power.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(sink_battery); | |
| static const struct device_type sink_battery_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = sink_battery_groups, | |
| }; | |
| /* -------------------------------------------------------------------------- */ | |
| /* Standard Power Range (SPR) Programmable Power Supply (PPS) */ | |
| static ssize_t | |
| pps_power_limited_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & BIT(27))); | |
| } | |
| static DEVICE_ATTR_RO(pps_power_limited); | |
| static ssize_t | |
| pps_max_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_max_voltage(to_pdo(dev)->pdo)); | |
| } | |
| static ssize_t | |
| pps_min_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_min_voltage(to_pdo(dev)->pdo)); | |
| } | |
| static ssize_t | |
| pps_max_current_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umA\n", pdo_pps_apdo_max_current(to_pdo(dev)->pdo)); | |
| } | |
| static struct device_attribute pps_max_voltage_attr = { | |
| .attr = { | |
| .name = "maximum_voltage", | |
| .mode = 0444, | |
| }, | |
| .show = pps_max_voltage_show, | |
| }; | |
| static struct device_attribute pps_min_voltage_attr = { | |
| .attr = { | |
| .name = "minimum_voltage", | |
| .mode = 0444, | |
| }, | |
| .show = pps_min_voltage_show, | |
| }; | |
| static struct device_attribute pps_max_current_attr = { | |
| .attr = { | |
| .name = "maximum_current", | |
| .mode = 0444, | |
| }, | |
| .show = pps_max_current_show, | |
| }; | |
| static struct attribute *source_pps_attrs[] = { | |
| &dev_attr_pps_power_limited.attr, | |
| &pps_max_voltage_attr.attr, | |
| &pps_min_voltage_attr.attr, | |
| &pps_max_current_attr.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(source_pps); | |
| static const struct device_type source_pps_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = source_pps_groups, | |
| }; | |
| static struct attribute *sink_pps_attrs[] = { | |
| &pps_max_voltage_attr.attr, | |
| &pps_min_voltage_attr.attr, | |
| &pps_max_current_attr.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(sink_pps); | |
| static const struct device_type sink_pps_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = sink_pps_groups, | |
| }; | |
| /* -------------------------------------------------------------------------- */ | |
| /* Standard Power Range (SPR) Adjustable Voltage Supply (AVS) */ | |
| static ssize_t | |
| spr_avs_9v_to_15v_max_current_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umA\n", | |
| pdo_spr_avs_apdo_9v_to_15v_max_current_ma(to_pdo(dev)->pdo)); | |
| } | |
| static ssize_t | |
| spr_avs_15v_to_20v_max_current_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%umA\n", | |
| pdo_spr_avs_apdo_15v_to_20v_max_current_ma(to_pdo(dev)->pdo)); | |
| } | |
| static ssize_t | |
| spr_avs_src_peak_current_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", | |
| pdo_spr_avs_apdo_src_peak_current(to_pdo(dev)->pdo)); | |
| } | |
| static struct device_attribute spr_avs_9v_to_15v_max_current_attr = { | |
| .attr = { | |
| .name = "maximum_current_9V_to_15V", | |
| .mode = 0444, | |
| }, | |
| .show = spr_avs_9v_to_15v_max_current_show, | |
| }; | |
| static struct device_attribute spr_avs_15v_to_20v_max_current_attr = { | |
| .attr = { | |
| .name = "maximum_current_15V_to_20V", | |
| .mode = 0444, | |
| }, | |
| .show = spr_avs_15v_to_20v_max_current_show, | |
| }; | |
| static struct device_attribute spr_avs_src_peak_current_attr = { | |
| .attr = { | |
| .name = "peak_current", | |
| .mode = 0444, | |
| }, | |
| .show = spr_avs_src_peak_current_show, | |
| }; | |
| static struct attribute *source_spr_avs_attrs[] = { | |
| &spr_avs_9v_to_15v_max_current_attr.attr, | |
| &spr_avs_15v_to_20v_max_current_attr.attr, | |
| &spr_avs_src_peak_current_attr.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(source_spr_avs); | |
| static const struct device_type source_spr_avs_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = source_spr_avs_groups, | |
| }; | |
| static struct attribute *sink_spr_avs_attrs[] = { | |
| &spr_avs_9v_to_15v_max_current_attr.attr, | |
| &spr_avs_15v_to_20v_max_current_attr.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(sink_spr_avs); | |
| static const struct device_type sink_spr_avs_type = { | |
| .name = "pdo", | |
| .release = pdo_release, | |
| .groups = sink_spr_avs_groups, | |
| }; | |
| /* -------------------------------------------------------------------------- */ | |
| static const char * const supply_name[] = { | |
| [PDO_TYPE_FIXED] = "fixed_supply", | |
| [PDO_TYPE_BATT] = "battery", | |
| [PDO_TYPE_VAR] = "variable_supply", | |
| }; | |
| static const char * const apdo_supply_name[] = { | |
| [APDO_TYPE_PPS] = "programmable_supply", | |
| [APDO_TYPE_SPR_AVS] = "spr_adjustable_voltage_supply", | |
| }; | |
| static const struct device_type *source_type[] = { | |
| [PDO_TYPE_FIXED] = &source_fixed_supply_type, | |
| [PDO_TYPE_BATT] = &source_battery_type, | |
| [PDO_TYPE_VAR] = &source_variable_supply_type, | |
| }; | |
| static const struct device_type *source_apdo_type[] = { | |
| [APDO_TYPE_PPS] = &source_pps_type, | |
| [APDO_TYPE_SPR_AVS] = &source_spr_avs_type, | |
| }; | |
| static const struct device_type *sink_type[] = { | |
| [PDO_TYPE_FIXED] = &sink_fixed_supply_type, | |
| [PDO_TYPE_BATT] = &sink_battery_type, | |
| [PDO_TYPE_VAR] = &sink_variable_supply_type, | |
| }; | |
| static const struct device_type *sink_apdo_type[] = { | |
| [APDO_TYPE_PPS] = &sink_pps_type, | |
| [APDO_TYPE_SPR_AVS] = &sink_spr_avs_type, | |
| }; | |
| /* REVISIT: Export when EPR_*_Capabilities need to be supported. */ | |
| static int add_pdo(struct usb_power_delivery_capabilities *cap, u32 pdo, int position) | |
| { | |
| const struct device_type *type; | |
| const char *name; | |
| struct pdo *p; | |
| int ret; | |
| p = kzalloc_obj(*p); | |
| if (!p) | |
| return -ENOMEM; | |
| p->pdo = pdo; | |
| p->object_position = position; | |
| if (pdo_type(pdo) == PDO_TYPE_APDO) { | |
| /* | |
| * FIXME: Only PPS, SPR_AVS supported for now! | |
| * Skipping others. | |
| */ | |
| if (pdo_apdo_type(pdo) != APDO_TYPE_PPS && | |
| pdo_apdo_type(pdo) != APDO_TYPE_SPR_AVS) { | |
| dev_warn(&cap->dev, "Unknown APDO type. PDO 0x%08x\n", pdo); | |
| kfree(p); | |
| return 0; | |
| } | |
| if (is_source(cap->role)) | |
| type = source_apdo_type[pdo_apdo_type(pdo)]; | |
| else | |
| type = sink_apdo_type[pdo_apdo_type(pdo)]; | |
| name = apdo_supply_name[pdo_apdo_type(pdo)]; | |
| } else { | |
| if (is_source(cap->role)) | |
| type = source_type[pdo_type(pdo)]; | |
| else | |
| type = sink_type[pdo_type(pdo)]; | |
| name = supply_name[pdo_type(pdo)]; | |
| } | |
| p->dev.parent = &cap->dev; | |
| p->dev.type = type; | |
| dev_set_name(&p->dev, "%u:%s", position + 1, name); | |
| ret = device_register(&p->dev); | |
| if (ret) { | |
| put_device(&p->dev); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int remove_pdo(struct device *dev, void *data) | |
| { | |
| device_unregister(dev); | |
| return 0; | |
| } | |
| /* -------------------------------------------------------------------------- */ | |
| static const char * const cap_name[] = { | |
| [TYPEC_SINK] = "sink-capabilities", | |
| [TYPEC_SOURCE] = "source-capabilities", | |
| }; | |
| static void pd_capabilities_release(struct device *dev) | |
| { | |
| kfree(to_usb_power_delivery_capabilities(dev)); | |
| } | |
| static const struct device_type pd_capabilities_type = { | |
| .name = "capabilities", | |
| .release = pd_capabilities_release, | |
| }; | |
| /** | |
| * usb_power_delivery_register_capabilities - Register a set of capabilities. | |
| * @pd: The USB PD instance that the capabilities belong to. | |
| * @desc: Description of the Capabilities Message. | |
| * | |
| * This function registers a Capabilities Message described in @desc. The | |
| * capabilities will have their own sub-directory under @pd in sysfs. | |
| * | |
| * The function returns pointer to struct usb_power_delivery_capabilities, or | |
| * ERR_PRT(errno). | |
| */ | |
| struct usb_power_delivery_capabilities * | |
| usb_power_delivery_register_capabilities(struct usb_power_delivery *pd, | |
| struct usb_power_delivery_capabilities_desc *desc) | |
| { | |
| struct usb_power_delivery_capabilities *cap; | |
| int ret; | |
| int i; | |
| cap = kzalloc_obj(*cap); | |
| if (!cap) | |
| return ERR_PTR(-ENOMEM); | |
| cap->pd = pd; | |
| cap->role = desc->role; | |
| cap->dev.parent = &pd->dev; | |
| cap->dev.type = &pd_capabilities_type; | |
| dev_set_name(&cap->dev, "%s", cap_name[cap->role]); | |
| ret = device_register(&cap->dev); | |
| if (ret) { | |
| put_device(&cap->dev); | |
| return ERR_PTR(ret); | |
| } | |
| for (i = 0; i < PDO_MAX_OBJECTS && desc->pdo[i]; i++) { | |
| ret = add_pdo(cap, desc->pdo[i], i); | |
| if (ret) { | |
| usb_power_delivery_unregister_capabilities(cap); | |
| return ERR_PTR(ret); | |
| } | |
| } | |
| return cap; | |
| } | |
| EXPORT_SYMBOL_GPL(usb_power_delivery_register_capabilities); | |
| /** | |
| * usb_power_delivery_unregister_capabilities - Unregister a set of capabilities | |
| * @cap: The capabilities | |
| */ | |
| void usb_power_delivery_unregister_capabilities(struct usb_power_delivery_capabilities *cap) | |
| { | |
| if (!cap) | |
| return; | |
| device_for_each_child(&cap->dev, NULL, remove_pdo); | |
| device_unregister(&cap->dev); | |
| } | |
| EXPORT_SYMBOL_GPL(usb_power_delivery_unregister_capabilities); | |
| /* -------------------------------------------------------------------------- */ | |
| static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct usb_power_delivery *pd = to_usb_power_delivery(dev); | |
| return sysfs_emit(buf, "%u.%u\n", (pd->revision >> 8) & 0xff, (pd->revision >> 4) & 0xf); | |
| } | |
| static DEVICE_ATTR_RO(revision); | |
| static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct usb_power_delivery *pd = to_usb_power_delivery(dev); | |
| return sysfs_emit(buf, "%u.%u\n", (pd->version >> 8) & 0xff, (pd->version >> 4) & 0xf); | |
| } | |
| static DEVICE_ATTR_RO(version); | |
| static struct attribute *pd_attrs[] = { | |
| &dev_attr_revision.attr, | |
| &dev_attr_version.attr, | |
| NULL | |
| }; | |
| static umode_t pd_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) | |
| { | |
| struct usb_power_delivery *pd = to_usb_power_delivery(kobj_to_dev(kobj)); | |
| if (attr == &dev_attr_version.attr && !pd->version) | |
| return 0; | |
| return attr->mode; | |
| } | |
| static const struct attribute_group pd_group = { | |
| .is_visible = pd_attr_is_visible, | |
| .attrs = pd_attrs, | |
| }; | |
| __ATTRIBUTE_GROUPS(pd); | |
| static void pd_release(struct device *dev) | |
| { | |
| struct usb_power_delivery *pd = to_usb_power_delivery(dev); | |
| ida_free(&pd_ida, pd->id); | |
| kfree(pd); | |
| } | |
| static const struct device_type pd_type = { | |
| .name = "usb_power_delivery", | |
| .release = pd_release, | |
| .groups = pd_groups, | |
| }; | |
| struct usb_power_delivery *usb_power_delivery_find(const char *name) | |
| { | |
| struct device *dev; | |
| dev = class_find_device_by_name(&pd_class, name); | |
| return dev ? to_usb_power_delivery(dev) : NULL; | |
| } | |
| /** | |
| * usb_power_delivery_register - Register USB Power Delivery Support. | |
| * @parent: Parent device. | |
| * @desc: Description of the USB PD contract. | |
| * | |
| * This routine can be used to register USB Power Delivery capabilities that a | |
| * device or devices can support. These capabilities represent all the | |
| * capabilities that can be negotiated with a partner, so not only the Power | |
| * Capabilities that are negotiated using the USB PD Capabilities Message. | |
| * | |
| * The USB Power Delivery Support object that this routine generates can be used | |
| * as the parent object for all the actual USB Power Delivery Messages and | |
| * objects that can be negotiated with the partner. | |
| * | |
| * Returns handle to struct usb_power_delivery or ERR_PTR. | |
| */ | |
| struct usb_power_delivery * | |
| usb_power_delivery_register(struct device *parent, struct usb_power_delivery_desc *desc) | |
| { | |
| struct usb_power_delivery *pd; | |
| int ret; | |
| pd = kzalloc_obj(*pd); | |
| if (!pd) | |
| return ERR_PTR(-ENOMEM); | |
| ret = ida_alloc(&pd_ida, GFP_KERNEL); | |
| if (ret < 0) { | |
| kfree(pd); | |
| return ERR_PTR(ret); | |
| } | |
| pd->id = ret; | |
| pd->revision = desc->revision; | |
| pd->version = desc->version; | |
| pd->dev.parent = parent; | |
| pd->dev.type = &pd_type; | |
| pd->dev.class = &pd_class; | |
| dev_set_name(&pd->dev, "pd%d", pd->id); | |
| ret = device_register(&pd->dev); | |
| if (ret) { | |
| put_device(&pd->dev); | |
| return ERR_PTR(ret); | |
| } | |
| return pd; | |
| } | |
| EXPORT_SYMBOL_GPL(usb_power_delivery_register); | |
| /** | |
| * usb_power_delivery_unregister - Unregister USB Power Delivery Support. | |
| * @pd: The USB PD contract. | |
| */ | |
| void usb_power_delivery_unregister(struct usb_power_delivery *pd) | |
| { | |
| if (IS_ERR_OR_NULL(pd)) | |
| return; | |
| device_unregister(&pd->dev); | |
| } | |
| EXPORT_SYMBOL_GPL(usb_power_delivery_unregister); | |
| /** | |
| * usb_power_delivery_link_device - Link device to its USB PD object. | |
| * @pd: The USB PD instance. | |
| * @dev: The device. | |
| * | |
| * This function can be used to create a symlink named "usb_power_delivery" for | |
| * @dev that points to @pd. | |
| */ | |
| int usb_power_delivery_link_device(struct usb_power_delivery *pd, struct device *dev) | |
| { | |
| int ret; | |
| if (IS_ERR_OR_NULL(pd) || !dev) | |
| return 0; | |
| ret = sysfs_create_link(&dev->kobj, &pd->dev.kobj, "usb_power_delivery"); | |
| if (ret) | |
| return ret; | |
| get_device(&pd->dev); | |
| get_device(dev); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(usb_power_delivery_link_device); | |
| /** | |
| * usb_power_delivery_unlink_device - Unlink device from its USB PD object. | |
| * @pd: The USB PD instance. | |
| * @dev: The device. | |
| * | |
| * Remove the symlink that was previously created with pd_link_device(). | |
| */ | |
| void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct device *dev) | |
| { | |
| if (IS_ERR_OR_NULL(pd) || !dev) | |
| return; | |
| sysfs_remove_link(&dev->kobj, "usb_power_delivery"); | |
| put_device(&pd->dev); | |
| put_device(dev); | |
| } | |
| EXPORT_SYMBOL_GPL(usb_power_delivery_unlink_device); | |
| /* -------------------------------------------------------------------------- */ | |
| int __init usb_power_delivery_init(void) | |
| { | |
| return class_register(&pd_class); | |
| } | |
| void __exit usb_power_delivery_exit(void) | |
| { | |
| ida_destroy(&pd_ida); | |
| class_unregister(&pd_class); | |
| } |