/*
* drivers/extcon/extcon.c - External Connector (extcon) framework.
*
* External connector (extcon) class driver
*
* Copyright (C) 2012 Samsung Electronics
* Author: Donggeun Kim <dg77.kim@samsung.com>
* Author: MyungJoo Ham <myungjoo.ham@samsung.com>
*
* based on android/drivers/switch/switch_class.c
* Copyright (C) 2008 Google, Inc.
* Author: Mike Lockwood <lockwood@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/extcon.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
/*
* extcon_cable_name suggests the standard cable names for commonly used
* cable types.
*
* However, please do not use extcon_cable_name directly for extcon_dev
* struct's supported_cable pointer unless your device really supports
* every single port-type of the following cable names. Please choose cable
* names that are actually used in your extcon device.
*/
const char extcon_cable_name[][CABLE_NAME_MAX + 1] = {
[EXTCON_USB] = "USB",
[EXTCON_USB_HOST] = "USB-Host",
[EXTCON_TA] = "TA",
[EXTCON_FAST_CHARGER] = "Fast-charger",
[EXTCON_SLOW_CHARGER] = "Slow-charger",
[EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream",
[EXTCON_HDMI] = "HDMI",
[EXTCON_MHL] = "MHL",
[EXTCON_DVI] = "DVI",
[EXTCON_VGA] = "VGA",
[EXTCON_DOCK] = "Dock",
[EXTCON_LINE_IN] = "Line-in",
[EXTCON_LINE_OUT] = "Line-out",
[EXTCON_MIC_IN] = "Microphone",
[EXTCON_HEADPHONE_OUT] = "Headphone",
[EXTCON_SPDIF_IN] = "SPDIF-in",
[EXTCON_SPDIF_OUT] = "SPDIF-out",
[EXTCON_VIDEO_IN] = "Video-in",
[EXTCON_VIDEO_OUT] = "Video-out",
[EXTCON_MECHANICAL] = "Mechanical",
};
static struct class *extcon_class;
#if defined(CONFIG_ANDROID)
static struct class_compat *switch_class;
#endif /* CONFIG_ANDROID */
static LIST_HEAD(extcon_dev_list);
static DEFINE_MUTEX(extcon_dev_list_lock);
/**
* check_mutually_exclusive - Check if new_state violates mutually_exclusive
* condition.
* @edev: the extcon device
* @new_state: new cable attach status for @edev
*
* Returns 0 if nothing violates. Returns the index + 1 for the first
* violated condition.
*/
static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state)
{
int i = 0;
if (!edev->mutually_exclusive)
return 0;
for (i = 0; edev->mutually_exclusive[i]; i++) {
int weight;
u32 correspondants = new_state & edev->mutually_exclusive[i];
/* calculate the total number of bits set */
weight = hweight32(correspondants);
if (weight > 1)
return i + 1;
}
return 0;
}
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int i, count = 0;
struct extcon_dev *edev = dev_get_drvdata(dev);
if (edev->print_state) {
int ret = edev->print_state(edev, buf);
if (ret >= 0)
return ret;
/* Use default if failed */
}
if (edev->max_supported == 0)
return sprintf(buf, "%u\n", edev->state);
for (i = 0; i < SUPPORTED_CABLE_MAX; i++) {
if (!edev->supported_cable[i])
break;
count += sprintf(buf + count, "%s=%d\n",
edev->supported_cable[i],
!!(edev->state & (1 << i)));
}
return count;
}
static ssize_t state_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
u32 state;