brintos

brintos / linux-shallow public Read only

0
0
Text · 37.1 KiB · e7f55c0 Raw
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