1494 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * drivers/extcon/extcon.c - External Connector (extcon) framework.4 *5 * Copyright (C) 2015 Samsung Electronics6 * Author: Chanwoo Choi <cw00.choi@samsung.com>7 *8 * Copyright (C) 2012 Samsung Electronics9 * Author: Donggeun Kim <dg77.kim@samsung.com>10 * Author: MyungJoo Ham <myungjoo.ham@samsung.com>11 *12 * based on android/drivers/switch/switch_class.c13 * Copyright (C) 2008 Google, Inc.14 * Author: Mike Lockwood <lockwood@android.com>15 */16 17#include <linux/module.h>18#include <linux/types.h>19#include <linux/idr.h>20#include <linux/init.h>21#include <linux/device.h>22#include <linux/fs.h>23#include <linux/err.h>24#include <linux/of.h>25#include <linux/slab.h>26#include <linux/sysfs.h>27 28#include "extcon.h"29 30#define SUPPORTED_CABLE_MAX 3231 32static const struct __extcon_info {33 unsigned int type;34 unsigned int id;35 const char *name;36 37} extcon_info[] = {38 [EXTCON_NONE] = {39 .type = EXTCON_TYPE_MISC,40 .id = EXTCON_NONE,41 .name = "NONE",42 },43 44 /* USB external connector */45 [EXTCON_USB] = {46 .type = EXTCON_TYPE_USB,47 .id = EXTCON_USB,48 .name = "USB",49 },50 [EXTCON_USB_HOST] = {51 .type = EXTCON_TYPE_USB,52 .id = EXTCON_USB_HOST,53 .name = "USB-HOST",54 },55 56 /* Charging external connector */57 [EXTCON_CHG_USB_SDP] = {58 .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,59 .id = EXTCON_CHG_USB_SDP,60 .name = "SDP",61 },62 [EXTCON_CHG_USB_DCP] = {63 .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,64 .id = EXTCON_CHG_USB_DCP,65 .name = "DCP",66 },67 [EXTCON_CHG_USB_CDP] = {68 .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,69 .id = EXTCON_CHG_USB_CDP,70 .name = "CDP",71 },72 [EXTCON_CHG_USB_ACA] = {73 .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,74 .id = EXTCON_CHG_USB_ACA,75 .name = "ACA",76 },77 [EXTCON_CHG_USB_FAST] = {78 .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,79 .id = EXTCON_CHG_USB_FAST,80 .name = "FAST-CHARGER",81 },82 [EXTCON_CHG_USB_SLOW] = {83 .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,84 .id = EXTCON_CHG_USB_SLOW,85 .name = "SLOW-CHARGER",86 },87 [EXTCON_CHG_WPT] = {88 .type = EXTCON_TYPE_CHG,89 .id = EXTCON_CHG_WPT,90 .name = "WPT",91 },92 [EXTCON_CHG_USB_PD] = {93 .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,94 .id = EXTCON_CHG_USB_PD,95 .name = "PD",96 },97 98 /* Jack external connector */99 [EXTCON_JACK_MICROPHONE] = {100 .type = EXTCON_TYPE_JACK,101 .id = EXTCON_JACK_MICROPHONE,102 .name = "MICROPHONE",103 },104 [EXTCON_JACK_HEADPHONE] = {105 .type = EXTCON_TYPE_JACK,106 .id = EXTCON_JACK_HEADPHONE,107 .name = "HEADPHONE",108 },109 [EXTCON_JACK_LINE_IN] = {110 .type = EXTCON_TYPE_JACK,111 .id = EXTCON_JACK_LINE_IN,112 .name = "LINE-IN",113 },114 [EXTCON_JACK_LINE_OUT] = {115 .type = EXTCON_TYPE_JACK,116 .id = EXTCON_JACK_LINE_OUT,117 .name = "LINE-OUT",118 },119 [EXTCON_JACK_VIDEO_IN] = {120 .type = EXTCON_TYPE_JACK,121 .id = EXTCON_JACK_VIDEO_IN,122 .name = "VIDEO-IN",123 },124 [EXTCON_JACK_VIDEO_OUT] = {125 .type = EXTCON_TYPE_JACK,126 .id = EXTCON_JACK_VIDEO_OUT,127 .name = "VIDEO-OUT",128 },129 [EXTCON_JACK_SPDIF_IN] = {130 .type = EXTCON_TYPE_JACK,131 .id = EXTCON_JACK_SPDIF_IN,132 .name = "SPDIF-IN",133 },134 [EXTCON_JACK_SPDIF_OUT] = {135 .type = EXTCON_TYPE_JACK,136 .id = EXTCON_JACK_SPDIF_OUT,137 .name = "SPDIF-OUT",138 },139 140 /* Display external connector */141 [EXTCON_DISP_HDMI] = {142 .type = EXTCON_TYPE_DISP,143 .id = EXTCON_DISP_HDMI,144 .name = "HDMI",145 },146 [EXTCON_DISP_MHL] = {147 .type = EXTCON_TYPE_DISP,148 .id = EXTCON_DISP_MHL,149 .name = "MHL",150 },151 [EXTCON_DISP_DVI] = {152 .type = EXTCON_TYPE_DISP,153 .id = EXTCON_DISP_DVI,154 .name = "DVI",155 },156 [EXTCON_DISP_VGA] = {157 .type = EXTCON_TYPE_DISP,158 .id = EXTCON_DISP_VGA,159 .name = "VGA",160 },161 [EXTCON_DISP_DP] = {162 .type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB,163 .id = EXTCON_DISP_DP,164 .name = "DP",165 },166 [EXTCON_DISP_HMD] = {167 .type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB,168 .id = EXTCON_DISP_HMD,169 .name = "HMD",170 },171 [EXTCON_DISP_CVBS] = {172 .type = EXTCON_TYPE_DISP,173 .id = EXTCON_DISP_CVBS,174 .name = "CVBS",175 },176 [EXTCON_DISP_EDP] = {177 .type = EXTCON_TYPE_DISP,178 .id = EXTCON_DISP_EDP,179 .name = "EDP",180 },181 182 /* Miscellaneous external connector */183 [EXTCON_DOCK] = {184 .type = EXTCON_TYPE_MISC,185 .id = EXTCON_DOCK,186 .name = "DOCK",187 },188 [EXTCON_JIG] = {189 .type = EXTCON_TYPE_MISC,190 .id = EXTCON_JIG,191 .name = "JIG",192 },193 [EXTCON_MECHANICAL] = {194 .type = EXTCON_TYPE_MISC,195 .id = EXTCON_MECHANICAL,196 .name = "MECHANICAL",197 },198 199 { /* sentinel */ }200};201 202/**203 * struct extcon_cable - An internal data for an external connector.204 * @edev: the extcon device205 * @cable_index: the index of this cable in the edev206 * @attr_g: the attribute group for the cable207 * @attr_name: "name" sysfs entry208 * @attr_state: "state" sysfs entry209 * @attrs: the array pointing to attr_name and attr_state for attr_g210 * @usb_propval: the array of USB connector properties211 * @chg_propval: the array of charger connector properties212 * @jack_propval: the array of jack connector properties213 * @disp_propval: the array of display connector properties214 * @usb_bits: the bit array of the USB connector property capabilities215 * @chg_bits: the bit array of the charger connector property capabilities216 * @jack_bits: the bit array of the jack connector property capabilities217 * @disp_bits: the bit array of the display connector property capabilities218 */219struct extcon_cable {220 struct extcon_dev *edev;221 int cable_index;222 223 struct attribute_group attr_g;224 struct device_attribute attr_name;225 struct device_attribute attr_state;226 227 struct attribute *attrs[3]; /* to be fed to attr_g.attrs */228 229 union extcon_property_value usb_propval[EXTCON_PROP_USB_CNT];230 union extcon_property_value chg_propval[EXTCON_PROP_CHG_CNT];231 union extcon_property_value jack_propval[EXTCON_PROP_JACK_CNT];232 union extcon_property_value disp_propval[EXTCON_PROP_DISP_CNT];233 234 DECLARE_BITMAP(usb_bits, EXTCON_PROP_USB_CNT);235 DECLARE_BITMAP(chg_bits, EXTCON_PROP_CHG_CNT);236 DECLARE_BITMAP(jack_bits, EXTCON_PROP_JACK_CNT);237 DECLARE_BITMAP(disp_bits, EXTCON_PROP_DISP_CNT);238};239 240static struct class *extcon_class;241 242static DEFINE_IDA(extcon_dev_ids);243static LIST_HEAD(extcon_dev_list);244static DEFINE_MUTEX(extcon_dev_list_lock);245 246static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state)247{248 int i;249 250 if (!edev->mutually_exclusive)251 return 0;252 253 for (i = 0; edev->mutually_exclusive[i]; i++) {254 int weight;255 u32 correspondants = new_state & edev->mutually_exclusive[i];256 257 /* calculate the total number of bits set */258 weight = hweight32(correspondants);259 if (weight > 1)260 return i + 1;261 }262 263 return 0;264}265 266static int find_cable_index_by_id(struct extcon_dev *edev, const unsigned int id)267{268 int i;269 270 /* Find the index of extcon cable in edev->supported_cable */271 for (i = 0; i < edev->max_supported; i++) {272 if (edev->supported_cable[i] == id)273 return i;274 }275 276 return -EINVAL;277}278 279static int get_extcon_type(unsigned int prop)280{281 switch (prop) {282 case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX:283 return EXTCON_TYPE_USB;284 case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX:285 return EXTCON_TYPE_CHG;286 case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX:287 return EXTCON_TYPE_JACK;288 case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX:289 return EXTCON_TYPE_DISP;290 default:291 return -EINVAL;292 }293}294 295static bool is_extcon_attached(struct extcon_dev *edev, unsigned int index)296{297 return !!(edev->state & BIT(index));298}299 300static bool is_extcon_changed(struct extcon_dev *edev, int index,301 bool new_state)302{303 int state = !!(edev->state & BIT(index));304 return (state != new_state);305}306 307static bool is_extcon_property_supported(unsigned int id, unsigned int prop)308{309 int type;310 311 /* Check whether the property is supported or not. */312 type = get_extcon_type(prop);313 if (type < 0)314 return false;315 316 /* Check whether a specific extcon id supports the property or not. */317 return !!(extcon_info[id].type & type);318}319 320static int is_extcon_property_capability(struct extcon_dev *edev,321 unsigned int id, int index,unsigned int prop)322{323 struct extcon_cable *cable;324 int type, ret;325 326 /* Check whether the property is supported or not. */327 type = get_extcon_type(prop);328 if (type < 0)329 return type;330 331 cable = &edev->cables[index];332 333 switch (type) {334 case EXTCON_TYPE_USB:335 ret = test_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits);336 break;337 case EXTCON_TYPE_CHG:338 ret = test_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits);339 break;340 case EXTCON_TYPE_JACK:341 ret = test_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits);342 break;343 case EXTCON_TYPE_DISP:344 ret = test_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits);345 break;346 default:347 ret = -EINVAL;348 }349 350 return ret;351}352 353static void init_property(struct extcon_dev *edev, unsigned int id, int index)354{355 unsigned int type = extcon_info[id].type;356 struct extcon_cable *cable = &edev->cables[index];357 358 if (EXTCON_TYPE_USB & type)359 memset(cable->usb_propval, 0, sizeof(cable->usb_propval));360 if (EXTCON_TYPE_CHG & type)361 memset(cable->chg_propval, 0, sizeof(cable->chg_propval));362 if (EXTCON_TYPE_JACK & type)363 memset(cable->jack_propval, 0, sizeof(cable->jack_propval));364 if (EXTCON_TYPE_DISP & type)365 memset(cable->disp_propval, 0, sizeof(cable->disp_propval));366}367 368static ssize_t state_show(struct device *dev, struct device_attribute *attr,369 char *buf)370{371 int i, count = 0;372 struct extcon_dev *edev = dev_get_drvdata(dev);373 374 if (edev->max_supported == 0)375 return sysfs_emit(buf, "%u\n", edev->state);376 377 for (i = 0; i < edev->max_supported; i++) {378 count += sysfs_emit_at(buf, count, "%s=%d\n",379 extcon_info[edev->supported_cable[i]].name,380 !!(edev->state & BIT(i)));381 }382 383 return count;384}385static DEVICE_ATTR_RO(state);386 387static ssize_t name_show(struct device *dev, struct device_attribute *attr,388 char *buf)389{390 struct extcon_dev *edev = dev_get_drvdata(dev);391 392 return sysfs_emit(buf, "%s\n", edev->name);393}394static DEVICE_ATTR_RO(name);395 396static ssize_t cable_name_show(struct device *dev,397 struct device_attribute *attr, char *buf)398{399 struct extcon_cable *cable = container_of(attr, struct extcon_cable,400 attr_name);401 int i = cable->cable_index;402 403 return sysfs_emit(buf, "%s\n",404 extcon_info[cable->edev->supported_cable[i]].name);405}406 407static ssize_t cable_state_show(struct device *dev,408 struct device_attribute *attr, char *buf)409{410 struct extcon_cable *cable = container_of(attr, struct extcon_cable,411 attr_state);412 413 int i = cable->cable_index;414 415 return sysfs_emit(buf, "%d\n",416 extcon_get_state(cable->edev, cable->edev->supported_cable[i]));417}418 419/**420 * extcon_sync() - Synchronize the state for an external connector.421 * @edev: the extcon device422 * @id: the unique id indicating an external connector423 *424 * Note that this function send a notification in order to synchronize425 * the state and property of an external connector.426 *427 * Returns 0 if success or error number if fail.428 */429int extcon_sync(struct extcon_dev *edev, unsigned int id)430{431 char name_buf[120];432 char state_buf[120];433 char *prop_buf;434 char *envp[3];435 int env_offset = 0;436 int length;437 int index;438 int state;439 unsigned long flags;440 441 if (!edev)442 return -EINVAL;443 444 index = find_cable_index_by_id(edev, id);445 if (index < 0)446 return index;447 448 spin_lock_irqsave(&edev->lock, flags);449 state = !!(edev->state & BIT(index));450 spin_unlock_irqrestore(&edev->lock, flags);451 452 /*453 * Call functions in a raw notifier chain for the specific one454 * external connector.455 */456 raw_notifier_call_chain(&edev->nh[index], state, edev);457 458 /*459 * Call functions in a raw notifier chain for the all supported460 * external connectors.461 */462 raw_notifier_call_chain(&edev->nh_all, state, edev);463 464 spin_lock_irqsave(&edev->lock, flags);465 /* This could be in interrupt handler */466 prop_buf = (char *)get_zeroed_page(GFP_ATOMIC);467 if (!prop_buf) {468 /* Unlock early before uevent */469 spin_unlock_irqrestore(&edev->lock, flags);470 471 dev_err(&edev->dev, "out of memory in extcon_set_state\n");472 kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE);473 474 return -ENOMEM;475 }476 477 length = name_show(&edev->dev, NULL, prop_buf);478 if (length > 0) {479 if (prop_buf[length - 1] == '\n')480 prop_buf[length - 1] = 0;481 snprintf(name_buf, sizeof(name_buf), "NAME=%s", prop_buf);482 envp[env_offset++] = name_buf;483 }484 485 length = state_show(&edev->dev, NULL, prop_buf);486 if (length > 0) {487 if (prop_buf[length - 1] == '\n')488 prop_buf[length - 1] = 0;489 snprintf(state_buf, sizeof(state_buf), "STATE=%s", prop_buf);490 envp[env_offset++] = state_buf;491 }492 envp[env_offset] = NULL;493 494 /* Unlock early before uevent */495 spin_unlock_irqrestore(&edev->lock, flags);496 kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp);497 free_page((unsigned long)prop_buf);498 499 return 0;500}501EXPORT_SYMBOL_GPL(extcon_sync);502 503/**504 * extcon_get_state() - Get the state of an external connector.505 * @edev: the extcon device506 * @id: the unique id indicating an external connector507 *508 * Returns 0 if success or error number if fail.509 */510int extcon_get_state(struct extcon_dev *edev, const unsigned int id)511{512 int index, state;513 unsigned long flags;514 515 if (!edev)516 return -EINVAL;517 518 index = find_cable_index_by_id(edev, id);519 if (index < 0)520 return index;521 522 spin_lock_irqsave(&edev->lock, flags);523 state = is_extcon_attached(edev, index);524 spin_unlock_irqrestore(&edev->lock, flags);525 526 return state;527}528EXPORT_SYMBOL_GPL(extcon_get_state);529 530/**531 * extcon_set_state() - Set the state of an external connector.532 * @edev: the extcon device533 * @id: the unique id indicating an external connector534 * @state: the new state of an external connector.535 * the default semantics is true: attached / false: detached.536 *537 * Note that this function set the state of an external connector without538 * a notification. To synchronize the state of an external connector,539 * have to use extcon_set_state_sync() and extcon_sync().540 *541 * Returns 0 if success or error number if fail.542 */543int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool state)544{545 unsigned long flags;546 int index, ret = 0;547 548 if (!edev)549 return -EINVAL;550 551 index = find_cable_index_by_id(edev, id);552 if (index < 0)553 return index;554 555 spin_lock_irqsave(&edev->lock, flags);556 557 /* Check whether the external connector's state is changed. */558 if (!is_extcon_changed(edev, index, state))559 goto out;560 561 if (check_mutually_exclusive(edev,562 (edev->state & ~BIT(index)) | (state & BIT(index)))) {563 ret = -EPERM;564 goto out;565 }566 567 /*568 * Initialize the value of extcon property before setting569 * the detached state for an external connector.570 */571 if (!state)572 init_property(edev, id, index);573 574 /* Update the state for an external connector. */575 if (state)576 edev->state |= BIT(index);577 else578 edev->state &= ~(BIT(index));579out:580 spin_unlock_irqrestore(&edev->lock, flags);581 582 return ret;583}584EXPORT_SYMBOL_GPL(extcon_set_state);585 586/**587 * extcon_set_state_sync() - Set the state of an external connector with sync.588 * @edev: the extcon device589 * @id: the unique id indicating an external connector590 * @state: the new state of external connector.591 * the default semantics is true: attached / false: detached.592 *593 * Note that this function set the state of external connector594 * and synchronize the state by sending a notification.595 *596 * Returns 0 if success or error number if fail.597 */598int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool state)599{600 int ret;601 602 ret = extcon_set_state(edev, id, state);603 if (ret < 0)604 return ret;605 606 return extcon_sync(edev, id);607}608EXPORT_SYMBOL_GPL(extcon_set_state_sync);609 610/**611 * extcon_get_property() - Get the property value of an external connector.612 * @edev: the extcon device613 * @id: the unique id indicating an external connector614 * @prop: the property id indicating an extcon property615 * @prop_val: the pointer which store the value of extcon property616 *617 * Note that when getting the property value of external connector,618 * the external connector should be attached. If detached state, function619 * return 0 without property value. Also, the each property should be620 * included in the list of supported properties according to extcon type.621 *622 * Returns 0 if success or error number if fail.623 */624int extcon_get_property(struct extcon_dev *edev, unsigned int id,625 unsigned int prop,626 union extcon_property_value *prop_val)627{628 struct extcon_cable *cable;629 unsigned long flags;630 int index, ret = 0;631 632 *prop_val = (union extcon_property_value){0};633 634 if (!edev)635 return -EINVAL;636 637 /* Check whether the property is supported or not */638 if (!is_extcon_property_supported(id, prop))639 return -EINVAL;640 641 /* Find the cable index of external connector by using id */642 index = find_cable_index_by_id(edev, id);643 if (index < 0)644 return index;645 646 spin_lock_irqsave(&edev->lock, flags);647 648 /* Check whether the property is available or not. */649 if (!is_extcon_property_capability(edev, id, index, prop)) {650 spin_unlock_irqrestore(&edev->lock, flags);651 return -EPERM;652 }653 654 /*655 * Check whether the external connector is attached.656 * If external connector is detached, the user can not657 * get the property value.658 */659 if (!is_extcon_attached(edev, index)) {660 spin_unlock_irqrestore(&edev->lock, flags);661 return 0;662 }663 664 cable = &edev->cables[index];665 666 /* Get the property value according to extcon type */667 switch (prop) {668 case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX:669 *prop_val = cable->usb_propval[prop - EXTCON_PROP_USB_MIN];670 break;671 case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX:672 *prop_val = cable->chg_propval[prop - EXTCON_PROP_CHG_MIN];673 break;674 case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX:675 *prop_val = cable->jack_propval[prop - EXTCON_PROP_JACK_MIN];676 break;677 case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX:678 *prop_val = cable->disp_propval[prop - EXTCON_PROP_DISP_MIN];679 break;680 default:681 ret = -EINVAL;682 break;683 }684 685 spin_unlock_irqrestore(&edev->lock, flags);686 687 return ret;688}689EXPORT_SYMBOL_GPL(extcon_get_property);690 691/**692 * extcon_set_property() - Set the property value of an external connector.693 * @edev: the extcon device694 * @id: the unique id indicating an external connector695 * @prop: the property id indicating an extcon property696 * @prop_val: the pointer including the new value of extcon property697 *698 * Note that each property should be included in the list of supported699 * properties according to the extcon type.700 *701 * Returns 0 if success or error number if fail.702 */703int extcon_set_property(struct extcon_dev *edev, unsigned int id,704 unsigned int prop,705 union extcon_property_value prop_val)706{707 struct extcon_cable *cable;708 unsigned long flags;709 int index, ret = 0;710 711 if (!edev)712 return -EINVAL;713 714 /* Check whether the property is supported or not */715 if (!is_extcon_property_supported(id, prop))716 return -EINVAL;717 718 /* Find the cable index of external connector by using id */719 index = find_cable_index_by_id(edev, id);720 if (index < 0)721 return index;722 723 spin_lock_irqsave(&edev->lock, flags);724 725 /* Check whether the property is available or not. */726 if (!is_extcon_property_capability(edev, id, index, prop)) {727 spin_unlock_irqrestore(&edev->lock, flags);728 return -EPERM;729 }730 731 cable = &edev->cables[index];732 733 /* Set the property value according to extcon type */734 switch (prop) {735 case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX:736 cable->usb_propval[prop - EXTCON_PROP_USB_MIN] = prop_val;737 break;738 case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX:739 cable->chg_propval[prop - EXTCON_PROP_CHG_MIN] = prop_val;740 break;741 case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX:742 cable->jack_propval[prop - EXTCON_PROP_JACK_MIN] = prop_val;743 break;744 case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX:745 cable->disp_propval[prop - EXTCON_PROP_DISP_MIN] = prop_val;746 break;747 default:748 ret = -EINVAL;749 break;750 }751 752 spin_unlock_irqrestore(&edev->lock, flags);753 754 return ret;755}756EXPORT_SYMBOL_GPL(extcon_set_property);757 758/**759 * extcon_set_property_sync() - Set property of an external connector with sync.760 * @edev: the extcon device761 * @id: the unique id indicating an external connector762 * @prop: the property id indicating an extcon property763 * @prop_val: the pointer including the new value of extcon property764 *765 * Note that when setting the property value of external connector,766 * the external connector should be attached. The each property should767 * be included in the list of supported properties according to extcon type.768 *769 * Returns 0 if success or error number if fail.770 */771int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id,772 unsigned int prop,773 union extcon_property_value prop_val)774{775 int ret;776 777 ret = extcon_set_property(edev, id, prop, prop_val);778 if (ret < 0)779 return ret;780 781 return extcon_sync(edev, id);782}783EXPORT_SYMBOL_GPL(extcon_set_property_sync);784 785/**786 * extcon_get_property_capability() - Get the capability of the property787 * for an external connector.788 * @edev: the extcon device789 * @id: the unique id indicating an external connector790 * @prop: the property id indicating an extcon property791 *792 * Returns 1 if the property is available or 0 if not available.793 */794int extcon_get_property_capability(struct extcon_dev *edev, unsigned int id,795 unsigned int prop)796{797 int index;798 799 if (!edev)800 return -EINVAL;801 802 /* Check whether the property is supported or not */803 if (!is_extcon_property_supported(id, prop))804 return -EINVAL;805 806 /* Find the cable index of external connector by using id */807 index = find_cable_index_by_id(edev, id);808 if (index < 0)809 return index;810 811 return is_extcon_property_capability(edev, id, index, prop);812}813EXPORT_SYMBOL_GPL(extcon_get_property_capability);814 815/**816 * extcon_set_property_capability() - Set the capability of the property817 * for an external connector.818 * @edev: the extcon device819 * @id: the unique id indicating an external connector820 * @prop: the property id indicating an extcon property821 *822 * Note that this function set the capability of the property823 * for an external connector in order to mark the bit in capability824 * bitmap which mean the available state of the property.825 *826 * Returns 0 if success or error number if fail.827 */828int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id,829 unsigned int prop)830{831 struct extcon_cable *cable;832 int index, type, ret = 0;833 834 if (!edev)835 return -EINVAL;836 837 /* Check whether the property is supported or not. */838 if (!is_extcon_property_supported(id, prop))839 return -EINVAL;840 841 /* Find the cable index of external connector by using id. */842 index = find_cable_index_by_id(edev, id);843 if (index < 0)844 return index;845 846 type = get_extcon_type(prop);847 if (type < 0)848 return type;849 850 cable = &edev->cables[index];851 852 switch (type) {853 case EXTCON_TYPE_USB:854 __set_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits);855 break;856 case EXTCON_TYPE_CHG:857 __set_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits);858 break;859 case EXTCON_TYPE_JACK:860 __set_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits);861 break;862 case EXTCON_TYPE_DISP:863 __set_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits);864 break;865 default:866 ret = -EINVAL;867 }868 869 return ret;870}871EXPORT_SYMBOL_GPL(extcon_set_property_capability);872 873/**874 * extcon_get_extcon_dev() - Get the extcon device instance from the name.875 * @extcon_name: the extcon name provided with extcon_dev_register()876 *877 * Return the pointer of extcon device if success or ERR_PTR(err) if fail.878 * NOTE: This function returns -EPROBE_DEFER so it may only be called from879 * probe() functions.880 */881struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name)882{883 struct extcon_dev *sd;884 885 if (!extcon_name)886 return ERR_PTR(-EINVAL);887 888 mutex_lock(&extcon_dev_list_lock);889 list_for_each_entry(sd, &extcon_dev_list, entry) {890 if (!strcmp(sd->name, extcon_name))891 goto out;892 }893 sd = ERR_PTR(-EPROBE_DEFER);894out:895 mutex_unlock(&extcon_dev_list_lock);896 return sd;897}898EXPORT_SYMBOL_GPL(extcon_get_extcon_dev);899 900/**901 * extcon_register_notifier() - Register a notifier block to get notified by902 * any state changes from the extcon.903 * @edev: the extcon device904 * @id: the unique id indicating an external connector905 * @nb: a notifier block to be registered906 *907 * Note that the second parameter given to the callback of nb (val) is908 * the current state of an external connector and the third pameter909 * is the pointer of extcon device.910 *911 * Returns 0 if success or error number if fail.912 */913int extcon_register_notifier(struct extcon_dev *edev, unsigned int id,914 struct notifier_block *nb)915{916 unsigned long flags;917 int ret, idx;918 919 if (!edev || !nb)920 return -EINVAL;921 922 idx = find_cable_index_by_id(edev, id);923 if (idx < 0)924 return idx;925 926 spin_lock_irqsave(&edev->lock, flags);927 ret = raw_notifier_chain_register(&edev->nh[idx], nb);928 spin_unlock_irqrestore(&edev->lock, flags);929 930 return ret;931}932EXPORT_SYMBOL_GPL(extcon_register_notifier);933 934/**935 * extcon_unregister_notifier() - Unregister a notifier block from the extcon.936 * @edev: the extcon device937 * @id: the unique id indicating an external connector938 * @nb: a notifier block to be registered939 *940 * Returns 0 if success or error number if fail.941 */942int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id,943 struct notifier_block *nb)944{945 unsigned long flags;946 int ret, idx;947 948 if (!edev || !nb)949 return -EINVAL;950 951 idx = find_cable_index_by_id(edev, id);952 if (idx < 0)953 return idx;954 955 spin_lock_irqsave(&edev->lock, flags);956 ret = raw_notifier_chain_unregister(&edev->nh[idx], nb);957 spin_unlock_irqrestore(&edev->lock, flags);958 959 return ret;960}961EXPORT_SYMBOL_GPL(extcon_unregister_notifier);962 963/**964 * extcon_register_notifier_all() - Register a notifier block for all connectors.965 * @edev: the extcon device966 * @nb: a notifier block to be registered967 *968 * Note that this function registers a notifier block in order to receive969 * the state change of all supported external connectors from extcon device.970 * And the second parameter given to the callback of nb (val) is971 * the current state and the third pameter is the pointer of extcon device.972 *973 * Returns 0 if success or error number if fail.974 */975int extcon_register_notifier_all(struct extcon_dev *edev,976 struct notifier_block *nb)977{978 unsigned long flags;979 int ret;980 981 if (!edev || !nb)982 return -EINVAL;983 984 spin_lock_irqsave(&edev->lock, flags);985 ret = raw_notifier_chain_register(&edev->nh_all, nb);986 spin_unlock_irqrestore(&edev->lock, flags);987 988 return ret;989}990EXPORT_SYMBOL_GPL(extcon_register_notifier_all);991 992/**993 * extcon_unregister_notifier_all() - Unregister a notifier block from extcon.994 * @edev: the extcon device995 * @nb: a notifier block to be registered996 *997 * Returns 0 if success or error number if fail.998 */999int extcon_unregister_notifier_all(struct extcon_dev *edev,1000 struct notifier_block *nb)1001{1002 unsigned long flags;1003 int ret;1004 1005 if (!edev || !nb)1006 return -EINVAL;1007 1008 spin_lock_irqsave(&edev->lock, flags);1009 ret = raw_notifier_chain_unregister(&edev->nh_all, nb);1010 spin_unlock_irqrestore(&edev->lock, flags);1011 1012 return ret;1013}1014EXPORT_SYMBOL_GPL(extcon_unregister_notifier_all);1015 1016static struct attribute *extcon_attrs[] = {1017 &dev_attr_state.attr,1018 &dev_attr_name.attr,1019 NULL,1020};1021ATTRIBUTE_GROUPS(extcon);1022 1023static int create_extcon_class(void)1024{1025 if (extcon_class)1026 return 0;1027 1028 extcon_class = class_create("extcon");1029 if (IS_ERR(extcon_class))1030 return PTR_ERR(extcon_class);1031 extcon_class->dev_groups = extcon_groups;1032 1033 return 0;1034}1035 1036static void extcon_dev_release(struct device *dev)1037{1038}1039 1040static const char *muex_name = "mutually_exclusive";1041static void dummy_sysfs_dev_release(struct device *dev)1042{1043}1044 1045/*1046 * extcon_dev_allocate() - Allocate the memory of extcon device.1047 * @supported_cable: the array of the supported external connectors1048 * ending with EXTCON_NONE.1049 *1050 * Note that this function allocates the memory for extcon device 1051 * and initialize default setting for the extcon device.1052 *1053 * Returns the pointer memory of allocated extcon_dev if success1054 * or ERR_PTR(err) if fail.1055 */1056struct extcon_dev *extcon_dev_allocate(const unsigned int *supported_cable)1057{1058 struct extcon_dev *edev;1059 1060 if (!supported_cable)1061 return ERR_PTR(-EINVAL);1062 1063 edev = kzalloc(sizeof(*edev), GFP_KERNEL);1064 if (!edev)1065 return ERR_PTR(-ENOMEM);1066 1067 edev->max_supported = 0;1068 edev->supported_cable = supported_cable;1069 1070 return edev;1071}1072 1073/*1074 * extcon_dev_free() - Free the memory of extcon device.1075 * @edev: the extcon device1076 */1077void extcon_dev_free(struct extcon_dev *edev)1078{1079 kfree(edev);1080}1081EXPORT_SYMBOL_GPL(extcon_dev_free);1082 1083/**1084 * extcon_alloc_cables() - alloc the cables for extcon device1085 * @edev: extcon device which has cables1086 *1087 * Returns 0 if success or error number if fail.1088 */1089static int extcon_alloc_cables(struct extcon_dev *edev)1090{1091 int index;1092 char *str;1093 struct extcon_cable *cable;1094 1095 if (!edev)1096 return -EINVAL;1097 1098 if (!edev->max_supported)1099 return 0;1100 1101 edev->cables = kcalloc(edev->max_supported, sizeof(*edev->cables),1102 GFP_KERNEL);1103 if (!edev->cables)1104 return -ENOMEM;1105 1106 for (index = 0; index < edev->max_supported; index++) {1107 cable = &edev->cables[index];1108 1109 str = kasprintf(GFP_KERNEL, "cable.%d", index);1110 if (!str) {1111 for (index--; index >= 0; index--) {1112 cable = &edev->cables[index];1113 kfree(cable->attr_g.name);1114 }1115 1116 kfree(edev->cables);1117 return -ENOMEM;1118 }1119 1120 cable->edev = edev;1121 cable->cable_index = index;1122 cable->attrs[0] = &cable->attr_name.attr;1123 cable->attrs[1] = &cable->attr_state.attr;1124 cable->attrs[2] = NULL;1125 cable->attr_g.name = str;1126 cable->attr_g.attrs = cable->attrs;1127 1128 sysfs_attr_init(&cable->attr_name.attr);1129 cable->attr_name.attr.name = "name";1130 cable->attr_name.attr.mode = 0444;1131 cable->attr_name.show = cable_name_show;1132 1133 sysfs_attr_init(&cable->attr_state.attr);1134 cable->attr_state.attr.name = "state";1135 cable->attr_state.attr.mode = 0444;1136 cable->attr_state.show = cable_state_show;1137 }1138 1139 return 0;1140}1141 1142/**1143 * extcon_alloc_muex() - alloc the mutual exclusive for extcon device1144 * @edev: extcon device1145 *1146 * Returns 0 if success or error number if fail.1147 */1148static int extcon_alloc_muex(struct extcon_dev *edev)1149{1150 char *name;1151 int index;1152 1153 if (!edev)1154 return -EINVAL;1155 1156 if (!(edev->max_supported && edev->mutually_exclusive))1157 return 0;1158 1159 /* Count the size of mutually_exclusive array */1160 for (index = 0; edev->mutually_exclusive[index]; index++)1161 ;1162 1163 edev->attrs_muex = kcalloc(index + 1, sizeof(*edev->attrs_muex),1164 GFP_KERNEL);1165 if (!edev->attrs_muex)1166 return -ENOMEM;1167 1168 edev->d_attrs_muex = kcalloc(index, sizeof(*edev->d_attrs_muex),1169 GFP_KERNEL);1170 if (!edev->d_attrs_muex) {1171 kfree(edev->attrs_muex);1172 return -ENOMEM;1173 }1174 1175 for (index = 0; edev->mutually_exclusive[index]; index++) {1176 name = kasprintf(GFP_KERNEL, "0x%x",1177 edev->mutually_exclusive[index]);1178 if (!name) {1179 for (index--; index >= 0; index--)1180 kfree(edev->d_attrs_muex[index].attr.name);1181 1182 kfree(edev->d_attrs_muex);1183 kfree(edev->attrs_muex);1184 return -ENOMEM;1185 }1186 sysfs_attr_init(&edev->d_attrs_muex[index].attr);1187 edev->d_attrs_muex[index].attr.name = name;1188 edev->d_attrs_muex[index].attr.mode = 0000;1189 edev->attrs_muex[index] = &edev->d_attrs_muex[index].attr;1190 }1191 edev->attr_g_muex.name = muex_name;1192 edev->attr_g_muex.attrs = edev->attrs_muex;1193 1194 return 0;1195}1196 1197/**1198 * extcon_alloc_groups() - alloc the groups for extcon device1199 * @edev: extcon device1200 *1201 * Returns 0 if success or error number if fail.1202 */1203static int extcon_alloc_groups(struct extcon_dev *edev)1204{1205 int index;1206 1207 if (!edev)1208 return -EINVAL;1209 1210 if (!edev->max_supported)1211 return 0;1212 1213 edev->extcon_dev_type.groups = kcalloc(edev->max_supported + 2,1214 sizeof(*edev->extcon_dev_type.groups),1215 GFP_KERNEL);1216 if (!edev->extcon_dev_type.groups)1217 return -ENOMEM;1218 1219 edev->extcon_dev_type.name = dev_name(&edev->dev);1220 edev->extcon_dev_type.release = dummy_sysfs_dev_release;1221 1222 for (index = 0; index < edev->max_supported; index++)1223 edev->extcon_dev_type.groups[index] = &edev->cables[index].attr_g;1224 1225 if (edev->mutually_exclusive)1226 edev->extcon_dev_type.groups[index] = &edev->attr_g_muex;1227 1228 edev->dev.type = &edev->extcon_dev_type;1229 1230 return 0;1231}1232 1233/**1234 * extcon_dev_register() - Register an new extcon device1235 * @edev: the extcon device to be registered1236 *1237 * Among the members of edev struct, please set the "user initializing data"1238 * do not set the values of "internal data", which are initialized by1239 * this function.1240 *1241 * Note that before calling this funciton, have to allocate the memory1242 * of an extcon device by using the extcon_dev_allocate(). And the extcon1243 * dev should include the supported_cable information.1244 *1245 * Returns 0 if success or error number if fail.1246 */1247int extcon_dev_register(struct extcon_dev *edev)1248{1249 int ret, index;1250 1251 ret = create_extcon_class();1252 if (ret < 0)1253 return ret;1254 1255 if (!edev || !edev->supported_cable)1256 return -EINVAL;1257 1258 for (index = 0; edev->supported_cable[index] != EXTCON_NONE; index++);1259 1260 edev->max_supported = index;1261 if (index > SUPPORTED_CABLE_MAX) {1262 dev_err(&edev->dev,1263 "exceed the maximum number of supported cables\n");1264 return -EINVAL;1265 }1266 1267 edev->dev.class = extcon_class;1268 edev->dev.release = extcon_dev_release;1269 1270 edev->name = dev_name(edev->dev.parent);1271 if (IS_ERR_OR_NULL(edev->name)) {1272 dev_err(&edev->dev,1273 "extcon device name is null\n");1274 return -EINVAL;1275 }1276 1277 ret = ida_alloc(&extcon_dev_ids, GFP_KERNEL);1278 if (ret < 0)1279 return ret;1280 1281 edev->id = ret;1282 1283 ret = extcon_alloc_cables(edev);1284 if (ret < 0)1285 goto err_alloc_cables;1286 1287 ret = extcon_alloc_muex(edev);1288 if (ret < 0)1289 goto err_alloc_muex;1290 1291 ret = extcon_alloc_groups(edev);1292 if (ret < 0)1293 goto err_alloc_groups;1294 1295 spin_lock_init(&edev->lock);1296 if (edev->max_supported) {1297 edev->nh = kcalloc(edev->max_supported, sizeof(*edev->nh),1298 GFP_KERNEL);1299 if (!edev->nh) {1300 ret = -ENOMEM;1301 goto err_alloc_nh;1302 }1303 }1304 1305 for (index = 0; index < edev->max_supported; index++)1306 RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]);1307 1308 RAW_INIT_NOTIFIER_HEAD(&edev->nh_all);1309 1310 dev_set_drvdata(&edev->dev, edev);1311 dev_set_name(&edev->dev, "extcon%d", edev->id);1312 edev->state = 0;1313 1314 ret = device_register(&edev->dev);1315 if (ret) {1316 put_device(&edev->dev);1317 goto err_dev;1318 }1319 1320 mutex_lock(&extcon_dev_list_lock);1321 list_add(&edev->entry, &extcon_dev_list);1322 mutex_unlock(&extcon_dev_list_lock);1323 1324 return 0;1325 1326err_dev:1327 if (edev->max_supported)1328 kfree(edev->nh);1329err_alloc_nh:1330 if (edev->max_supported)1331 kfree(edev->extcon_dev_type.groups);1332err_alloc_groups:1333 if (edev->max_supported && edev->mutually_exclusive) {1334 for (index = 0; edev->mutually_exclusive[index]; index++)1335 kfree(edev->d_attrs_muex[index].attr.name);1336 kfree(edev->d_attrs_muex);1337 kfree(edev->attrs_muex);1338 }1339err_alloc_muex:1340 for (index = 0; index < edev->max_supported; index++)1341 kfree(edev->cables[index].attr_g.name);1342 if (edev->max_supported)1343 kfree(edev->cables);1344err_alloc_cables:1345 ida_free(&extcon_dev_ids, edev->id);1346 1347 return ret;1348}1349EXPORT_SYMBOL_GPL(extcon_dev_register);1350 1351/**1352 * extcon_dev_unregister() - Unregister the extcon device.1353 * @edev: the extcon device to be unregistered.1354 *1355 * Note that this does not call kfree(edev) because edev was not allocated1356 * by this class.1357 */1358void extcon_dev_unregister(struct extcon_dev *edev)1359{1360 int index;1361 1362 if (!edev)1363 return;1364 1365 mutex_lock(&extcon_dev_list_lock);1366 list_del(&edev->entry);1367 mutex_unlock(&extcon_dev_list_lock);1368 1369 if (!get_device(&edev->dev)) {1370 dev_err(&edev->dev, "Failed to unregister extcon_dev\n");1371 return;1372 }1373 1374 ida_free(&extcon_dev_ids, edev->id);1375 1376 device_unregister(&edev->dev);1377 1378 if (edev->mutually_exclusive && edev->max_supported) {1379 for (index = 0; edev->mutually_exclusive[index];1380 index++)1381 kfree(edev->d_attrs_muex[index].attr.name);1382 kfree(edev->d_attrs_muex);1383 kfree(edev->attrs_muex);1384 }1385 1386 for (index = 0; index < edev->max_supported; index++)1387 kfree(edev->cables[index].attr_g.name);1388 1389 if (edev->max_supported) {1390 kfree(edev->extcon_dev_type.groups);1391 kfree(edev->cables);1392 kfree(edev->nh);1393 }1394 1395 put_device(&edev->dev);1396}1397EXPORT_SYMBOL_GPL(extcon_dev_unregister);1398 1399#ifdef CONFIG_OF1400 1401/*1402 * extcon_find_edev_by_node - Find the extcon device from devicetree.1403 * @node : OF node identifying edev1404 *1405 * Return the pointer of extcon device if success or ERR_PTR(err) if fail.1406 */1407struct extcon_dev *extcon_find_edev_by_node(struct device_node *node)1408{1409 struct extcon_dev *edev;1410 1411 mutex_lock(&extcon_dev_list_lock);1412 list_for_each_entry(edev, &extcon_dev_list, entry)1413 if (edev->dev.parent && device_match_of_node(edev->dev.parent, node))1414 goto out;1415 edev = ERR_PTR(-EPROBE_DEFER);1416out:1417 mutex_unlock(&extcon_dev_list_lock);1418 1419 return edev;1420}1421 1422/*1423 * extcon_get_edev_by_phandle - Get the extcon device from devicetree.1424 * @dev : the instance to the given device1425 * @index : the index into list of extcon_dev1426 *1427 * Return the pointer of extcon device if success or ERR_PTR(err) if fail.1428 */1429struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index)1430{1431 struct device_node *node, *np = dev_of_node(dev);1432 struct extcon_dev *edev;1433 1434 if (!np) {1435 dev_dbg(dev, "device does not have a device node entry\n");1436 return ERR_PTR(-EINVAL);1437 }1438 1439 node = of_parse_phandle(np, "extcon", index);1440 if (!node) {1441 dev_dbg(dev, "failed to get phandle in %pOF node\n", np);1442 return ERR_PTR(-ENODEV);1443 }1444 1445 edev = extcon_find_edev_by_node(node);1446 of_node_put(node);1447 1448 return edev;1449}1450 1451#else1452 1453struct extcon_dev *extcon_find_edev_by_node(struct device_node *node)1454{1455 return ERR_PTR(-ENOSYS);1456}1457 1458struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index)1459{1460 return ERR_PTR(-ENOSYS);1461}1462 1463#endif /* CONFIG_OF */1464 1465EXPORT_SYMBOL_GPL(extcon_find_edev_by_node);1466EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle);1467 1468/**1469 * extcon_get_edev_name() - Get the name of the extcon device.1470 * @edev: the extcon device1471 */1472const char *extcon_get_edev_name(struct extcon_dev *edev)1473{1474 return !edev ? NULL : edev->name;1475}1476EXPORT_SYMBOL_GPL(extcon_get_edev_name);1477 1478static int __init extcon_class_init(void)1479{1480 return create_extcon_class();1481}1482module_init(extcon_class_init);1483 1484static void __exit extcon_class_exit(void)1485{1486 class_destroy(extcon_class);1487}1488module_exit(extcon_class_exit);1489 1490MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");1491MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");1492MODULE_DESCRIPTION("External Connector (extcon) framework");1493MODULE_LICENSE("GPL v2");1494